Home | History | Annotate | Download | only in webapp
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview
      7  * Class to communicate with the It2me Host component via Native Messaging.
      8  */
      9 
     10 'use strict';
     11 
     12 /** @suppress {duplicate} */
     13 var remoting = remoting || {};
     14 
     15 /**
     16  * @constructor
     17  */
     18 remoting.It2MeHostFacade = function() {
     19   /**
     20    * @type {number}
     21    * @private
     22    */
     23   this.nextId_ = 0;
     24 
     25   /**
     26    * @type {?chrome.runtime.Port}
     27    * @private
     28    */
     29   this.port_ = null;
     30 
     31   /**
     32    * @type {string}
     33    * @private
     34    */
     35   this.accessCode_ = '';
     36 
     37   /**
     38    * @type {number}
     39    * @private
     40    */
     41   this.accessCodeLifetime_ = 0;
     42 
     43   /**
     44    * @type {string}
     45    * @private
     46    */
     47   this.clientId_ = '';
     48 
     49   /**
     50    * @type {boolean}
     51    * @private
     52    */
     53   this.initialized_ = false;
     54 
     55   /**
     56    * @type {?function():void}
     57  * @private
     58    */
     59   this.onInitialized_ = function() {};
     60 
     61   /**
     62    * Called if Native Messaging host has failed to start.
     63    * @private
     64    * */
     65   this.onInitializationFailed_ = function() {};
     66 
     67   /**
     68    * Called if the It2Me Native Messaging host sends a malformed message:
     69    * missing required attributes, attributes with incorrect types, etc.
     70    * @param {remoting.Error} error
     71    * @private
     72    */
     73   this.onError_ = function(error) {};
     74 
     75   /**
     76    * @type {?function(remoting.HostSession.State):void}
     77    * @private
     78    */
     79   this.onStateChanged_ = function() {};
     80 
     81   /**
     82    * @type {?function(boolean):void}
     83    * @private
     84    */
     85   this.onNatPolicyChanged_ = function() {};
     86 };
     87 
     88 /**
     89  * Sets up connection to the Native Messaging host process and exchanges
     90  * 'hello' messages. If Native Messaging is not supported or if the it2me
     91  * native messaging host is not installed, onInitializationFailed is invoked.
     92  * Otherwise, onInitialized is invoked.
     93  *
     94  * @param {function():void} onInitialized Called after successful
     95  *     initialization.
     96  * @param {function():void} onInitializationFailed Called if cannot connect to
     97  *     the native messaging host.
     98  * @return {void}
     99  */
    100 remoting.It2MeHostFacade.prototype.initialize =
    101     function(onInitialized, onInitializationFailed) {
    102   this.onInitialized_ = onInitialized;
    103   this.onInitializationFailed_ = onInitializationFailed;
    104 
    105   try {
    106     this.port_ = chrome.runtime.connectNative(
    107         'com.google.chrome.remote_assistance');
    108     this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
    109     this.port_.onDisconnect.addListener(this.onHostDisconnect_.bind(this));
    110     this.port_.postMessage({type: 'hello'});
    111   } catch (err) {
    112     console.log('Native Messaging initialization failed: ',
    113                 /** @type {*} */ (err));
    114     onInitializationFailed();
    115     return;
    116   }
    117 };
    118 
    119 /**
    120  * @param {string} email The user's email address.
    121  * @param {string} authServiceWithToken Concatenation of the auth service
    122  *     (e.g. oauth2) and the access token.
    123  * @param {function(remoting.HostSession.State):void} onStateChanged Callback to
    124  *     invoke when the host state changes.
    125  * @param {function(boolean):void} onNatPolicyChanged Callback to invoke when
    126  *     the nat traversal policy changes.
    127  * @param {function(string):void} logDebugInfo Callback allowing the plugin
    128  *     to log messages to the debug log.
    129  * @param {string} xmppServerAddress XMPP server host name (or IP address) and
    130  *     port.
    131  * @param {boolean} xmppServerUseTls Whether to use TLS on connections to the
    132  *     XMPP server
    133  * @param {string} directoryBotJid XMPP JID for the remoting directory server
    134  *     bot.
    135  * @param {function(remoting.Error):void} onError Callback to invoke in case of
    136  *     an error.
    137  * @return {void}
    138  */
    139 remoting.It2MeHostFacade.prototype.connect =
    140     function(email, authServiceWithToken, onStateChanged, onNatPolicyChanged,
    141              logDebugInfo, xmppServerAddress, xmppServerUseTls, directoryBotJid,
    142              onError) {
    143   if (!this.port_) {
    144     console.error(
    145         'remoting.It2MeHostFacade.connect() without initialization.');
    146     onError(remoting.Error.UNEXPECTED);
    147     return;
    148   }
    149 
    150   this.onStateChanged_ = onStateChanged;
    151   this.onNatPolicyChanged_ = onNatPolicyChanged;
    152   this.onError_ = onError;
    153   this.port_.postMessage({
    154     type: 'connect',
    155     userName: email,
    156     authServiceWithToken: authServiceWithToken,
    157     xmppServerAddress: xmppServerAddress,
    158     xmppServerUseTls: xmppServerUseTls,
    159     directoryBotJid: directoryBotJid
    160   });
    161 };
    162 
    163 /**
    164  * Unhooks the |onStateChanged|, |onError|, |onNatPolicyChanged| and
    165  * |onInitalized| callbacks.  This is called when the client shuts down so that
    166  * the callbacks will not be invoked on a disposed client.
    167  *
    168  * @return {void}
    169  */
    170 remoting.It2MeHostFacade.prototype.unhookCallbacks = function() {
    171   this.onStateChanged_ = null;
    172   this.onNatPolicyChanged_ = null;
    173   this.onError_ = null;
    174   this.onInitialized_ = null;
    175 };
    176 
    177 /**
    178  * @return {void}
    179  */
    180 remoting.It2MeHostFacade.prototype.disconnect = function() {
    181   if (this.port_)
    182     this.port_.postMessage({type: 'disconnect'});
    183 };
    184 
    185 /**
    186  * @return {boolean}
    187  */
    188 remoting.It2MeHostFacade.prototype.initialized = function() {
    189   return this.initialized_;
    190 };
    191 
    192 /**
    193  * @return {string}
    194  */
    195 remoting.It2MeHostFacade.prototype.getAccessCode = function() {
    196   return this.accessCode_;
    197 };
    198 
    199 /**
    200  * @return {number}
    201  */
    202 remoting.It2MeHostFacade.prototype.getAccessCodeLifetime = function() {
    203   return this.accessCodeLifetime_;
    204 };
    205 
    206 /**
    207  * @return {string}
    208  */
    209 remoting.It2MeHostFacade.prototype.getClient = function() {
    210   return this.clientId_;
    211 };
    212 
    213 /**
    214  * Handler for incoming messages.
    215  *
    216  * @param {Object} message The received message.
    217  * @return {void}
    218  * @private
    219  */
    220 remoting.It2MeHostFacade.prototype.onIncomingMessage_ =
    221     function(message) {
    222   var type = getStringAttr(message, 'type');
    223 
    224   switch (type) {
    225     case 'helloResponse':
    226       var version = getStringAttr(message, 'version');
    227       console.log('Host version: ', version);
    228       this.initialized_ = true;
    229       // A "hello" request is sent immediately after the native messaging host
    230       // is started. We can proceed to the next task once we receive the
    231       // "helloReponse".
    232       if (this.onInitialized_) {
    233         this.onInitialized_();
    234       }
    235       break;
    236 
    237     case 'connectResponse':
    238       console.log('connectResponse received');
    239       // Response to the "connect" request. No action is needed until we
    240       // receive the corresponding "hostStateChanged" message.
    241       break;
    242 
    243     case 'disconnectResponse':
    244       console.log('disconnectResponse received');
    245       // Response to the "disconnect" request. No action is needed until we
    246       // receive the corresponding "hostStateChanged" message.
    247       break;
    248 
    249     case 'hostStateChanged':
    250       var stateString = getStringAttr(message, 'state');
    251       console.log('hostStateChanged received: ', stateString);
    252       var state = remoting.HostSession.State.fromString(stateString);
    253 
    254       switch (state) {
    255         case remoting.HostSession.State.RECEIVED_ACCESS_CODE:
    256           var accessCode = getStringAttr(message, 'accessCode');
    257           var accessCodeLifetime = getNumberAttr(message, 'accessCodeLifetime');
    258           this.onReceivedAccessCode_(accessCode, accessCodeLifetime);
    259           break;
    260 
    261         case remoting.HostSession.State.CONNECTED:
    262           var client = getStringAttr(message, 'client');
    263           this.onConnected_(client);
    264           break;
    265       }
    266       if (this.onStateChanged_) {
    267         this.onStateChanged_(state);
    268       }
    269       break;
    270 
    271     case 'natPolicyChanged':
    272       if (this.onNatPolicyChanged_) {
    273         var natTraversalEnabled =
    274             getBooleanAttr(message, 'natTraversalEnabled');
    275         this.onNatPolicyChanged_(natTraversalEnabled);
    276       }
    277       break;
    278 
    279     case 'error':
    280       console.error(getStringAttr(message, 'description'));
    281       if (this.onError_) {
    282         this.onError_(remoting.Error.UNEXPECTED);
    283       }
    284       break;
    285 
    286     default:
    287       throw 'Unexpected native message: ' + message;
    288   }
    289 };
    290 
    291 /**
    292  * @param {string} accessCode
    293  * @param {number} accessCodeLifetime
    294  * @return {void}
    295  * @private
    296  */
    297 remoting.It2MeHostFacade.prototype.onReceivedAccessCode_ =
    298     function(accessCode, accessCodeLifetime) {
    299   this.accessCode_ = accessCode;
    300   this.accessCodeLifetime_ = accessCodeLifetime;
    301 };
    302 
    303 /**
    304  * @param {string} clientId
    305  * @return {void}
    306  * @private
    307  */
    308 remoting.It2MeHostFacade.prototype.onConnected_ = function(clientId) {
    309   this.clientId_ = clientId;
    310 };
    311 
    312 /**
    313  * @return {void}
    314  * @private
    315  */
    316 remoting.It2MeHostFacade.prototype.onHostDisconnect_ = function() {
    317   if (!this.initialized_) {
    318     // If the host is disconnected before it is initialized, it probably means
    319     // the host is not propertly installed (or not installed at all).
    320     // E.g., if the host manifest is not present we get "Specified native
    321     // messaging host not found" error. If the host manifest is present but
    322     // the host binary cannot be found we get the "Native host has exited"
    323     // error.
    324     console.log('Native Messaging initialization failed: ' +
    325                 chrome.runtime.lastError.message);
    326     this.onInitializationFailed_();
    327   } else {
    328     console.error('Native Messaging port disconnected.');
    329     this.port_ = null;
    330     this.onError_(remoting.Error.UNEXPECTED);
    331   }
    332 }
    333