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.HostIt2MeNativeMessaging = function() {
     19   /**
     20    * @type {number}
     21    * @private
     22    */
     23   this.nextId_ = 0;
     24 
     25   /**
     26    * @type {?chrome.extension.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.onHostStarted_ = function() {};
     60 
     61   /**
     62    * Called if Native Messaging host has failed to start.
     63    * @private
     64    * */
     65   this.onHostInitFailed_ = 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, onHostInitFailed is invoked.
     92  * Otherwise, onHostStarted is invoked.
     93  *
     94  * @param {function():void} onHostStarted Called after successful
     95  *     initialization.
     96  * @param {function():void} onHostInitFailed Called if cannot connect to host.
     97  * @param {function(remoting.Error):void} onError Called on host error after
     98  *     successfully connecting to the host.
     99  * @return {void}
    100  */
    101 remoting.HostIt2MeNativeMessaging.prototype.initialize =
    102     function(onHostStarted, onHostInitFailed, onError) {
    103   this.onHostStarted_ = onHostStarted;
    104   this.onHostInitFailed_ = onHostInitFailed;
    105   this.onError_ = onError;
    106 
    107   try {
    108     this.port_ = chrome.runtime.connectNative(
    109         'com.google.chrome.remote_assistance');
    110     this.port_.onMessage.addListener(this.onIncomingMessage_.bind(this));
    111     this.port_.onDisconnect.addListener(this.onHostDisconnect_.bind(this));
    112     this.postMessage_({type: 'hello'});
    113   } catch (err) {
    114     console.log('Native Messaging initialization failed: ',
    115                 /** @type {*} */ (err));
    116     onHostInitFailed();
    117     return;
    118   }
    119 };
    120 
    121 /**
    122  * Attaches a new ID to the supplied message, and posts it to the Native
    123  * Messaging port.
    124  * |message| should have its 'type' field set, and any other fields set
    125  * depending on the message type.
    126  *
    127  * @param {{type: string}} message The message to post.
    128  * @return {void}
    129  * @private
    130  */
    131 remoting.HostIt2MeNativeMessaging.prototype.postMessage_ =
    132     function(message) {
    133   var id = this.nextId_++;
    134   message['id'] = id;
    135   this.port_.postMessage(message);
    136 };
    137 
    138 /**
    139  * Handler for incoming Native Messages.
    140  *
    141  * @param {Object} message The received message.
    142  * @return {void}
    143  * @private
    144  */
    145 remoting.HostIt2MeNativeMessaging.prototype.onIncomingMessage_ =
    146     function(message) {
    147   try {
    148     this.handleIncomingMessage_(message);
    149   } catch (e) {
    150     console.error(/** @type {*} */ (e));
    151     this.onError_(remoting.Error.UNEXPECTED);
    152   }
    153 }
    154 
    155 /**
    156  * Handler for incoming Native Messages.
    157  *
    158  * @param {Object} message The received message.
    159  * @return {void}
    160  * @private
    161  */
    162 remoting.HostIt2MeNativeMessaging.prototype.handleIncomingMessage_ =
    163     function(message) {
    164   var type = getStringAttr(message, 'type');
    165 
    166   switch (type) {
    167     case 'helloResponse':
    168       var version = getStringAttr(message, 'version');
    169       console.log('Host version: ', version);
    170       this.initialized_ = true;
    171       // A "hello" request is sent immediately after the native messaging host
    172       // is started. We can proceed to the next task once we receive the
    173       // "helloReponse".
    174       this.onHostStarted_();
    175       break;
    176 
    177     case 'connectResponse':
    178       console.log('connectResponse received');
    179       // Response to the "connect" request. No action is needed until we
    180       // receive the corresponding "hostStateChanged" message.
    181       break;
    182 
    183     case 'disconnectResponse':
    184       console.log('disconnectResponse received');
    185       // Response to the "disconnect" request. No action is needed until we
    186       // receive the corresponding "hostStateChanged" message.
    187       break;
    188 
    189     case 'hostStateChanged':
    190       var stateString = getStringAttr(message, 'state');
    191       console.log('hostStateChanged received: ', stateString);
    192       var state = remoting.HostSession.State.fromString(stateString);
    193 
    194       switch (state) {
    195         case remoting.HostSession.State.RECEIVED_ACCESS_CODE:
    196           var accessCode = getStringAttr(message, 'accessCode');
    197           var accessCodeLifetime = getNumberAttr(message, 'accessCodeLifetime');
    198           this.onReceivedAccessCode_(accessCode, accessCodeLifetime);
    199           break;
    200 
    201         case remoting.HostSession.State.CONNECTED:
    202           var client = getStringAttr(message, 'client');
    203           this.onConnected_(client);
    204           break;
    205       }
    206       this.onStateChanged_(state);
    207       break;
    208 
    209     case 'natPolicyChanged':
    210       var natTraversalEnabled = getBooleanAttr(message, 'natTraversalEnabled');
    211       this.onNatPolicyChanged_(natTraversalEnabled);
    212       break;
    213 
    214     case 'error':
    215       console.error(getStringAttr(message, 'description'));
    216       this.onError_(remoting.Error.UNEXPECTED);
    217       break;
    218 
    219     default:
    220       throw 'Unexpected native message: ' + message;
    221   }
    222 };
    223 
    224 /**
    225  * @param {string} email The user's email address.
    226  * @param {string} authServiceWithToken Concatenation of the auth service
    227  *     (e.g. oauth2) and the access token.
    228  * @param {function(remoting.HostSession.State):void} onStateChanged Callback to
    229  *     invoke when the host state changes.
    230  * @param {function(boolean):void} onNatPolicyChanged Callback to invoke when
    231  *     the nat traversal policy changes.
    232  * @param {string} xmppServerAddress XMPP server host name (or IP address) and
    233  *     port.
    234  * @param {boolean} xmppServerUseTls Whether to use TLS on connections to the
    235  *     XMPP server
    236  * @param {string} directoryBotJid XMPP JID for the remoting directory server
    237  *     bot.
    238  * @return {void}
    239  */
    240 remoting.HostIt2MeNativeMessaging.prototype.connect =
    241     function(email, authServiceWithToken, onStateChanged, onNatPolicyChanged,
    242              xmppServerAddress, xmppServerUseTls, directoryBotJid) {
    243   this.onStateChanged_ = onStateChanged;
    244   this.onNatPolicyChanged_ = onNatPolicyChanged;
    245   this.postMessage_({
    246       type: 'connect',
    247       userName: email,
    248       authServiceWithToken: authServiceWithToken,
    249       xmppServerAddress: xmppServerAddress,
    250       xmppServerUseTls: xmppServerUseTls,
    251       directoryBotJid: directoryBotJid});
    252 };
    253 
    254 /**
    255  * @return {void}
    256  */
    257 remoting.HostIt2MeNativeMessaging.prototype.disconnect =
    258     function() {
    259   this.postMessage_({
    260       type: 'disconnect'});
    261 };
    262 
    263 /**
    264  * @param {string} accessCode
    265  * @param {number} accessCodeLifetime
    266  * @return {void}
    267  * @private
    268  */
    269 remoting.HostIt2MeNativeMessaging.prototype.onReceivedAccessCode_ =
    270     function(accessCode, accessCodeLifetime) {
    271   this.accessCode_ = accessCode;
    272   this.accessCodeLifetime_ = accessCodeLifetime;
    273 };
    274 
    275 /**
    276  * @param {string} clientId
    277  * @return {void}
    278  * @private
    279  */
    280 remoting.HostIt2MeNativeMessaging.prototype.onConnected_ =
    281     function(clientId) {
    282   this.clientId_ = clientId;
    283 };
    284 
    285 /**
    286  * @return {void}
    287  * @private
    288  */
    289 remoting.HostIt2MeNativeMessaging.prototype.onHostDisconnect_ = function() {
    290   if (!this.initialized_) {
    291     // If the host is disconnected before it is initialized, it probably means
    292     // the host is not propertly installed (or not installed at all).
    293     // E.g., if the host manifest is not present we get "Specified native
    294     // messaging host not found" error. If the host manifest is present but
    295     // the host binary cannot be found we get the "Native host has exited"
    296     // error.
    297     console.log('Native Messaging initialization failed: ' +
    298                 chrome.runtime.lastError.message);
    299     this.onHostInitFailed_();
    300   } else {
    301     console.error('Native Messaging port disconnected.');
    302     this.onError_(remoting.Error.UNEXPECTED);
    303   }
    304 }
    305 
    306 /**
    307  * @return {string}
    308  */
    309 remoting.HostIt2MeNativeMessaging.prototype.getAccessCode = function() {
    310   return this.accessCode_
    311 };
    312 
    313 /**
    314  * @return {number}
    315  */
    316 remoting.HostIt2MeNativeMessaging.prototype.getAccessCodeLifetime = function() {
    317   return this.accessCodeLifetime_
    318 };
    319 
    320 /**
    321  * @return {string}
    322  */
    323 remoting.HostIt2MeNativeMessaging.prototype.getClient = function() {
    324   return this.clientId_;
    325 };
    326