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} */ 13var remoting = remoting || {}; 14 15/** 16 * @constructor 17 */ 18remoting.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 */ 100remoting.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 */ 139remoting.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 */ 170remoting.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 */ 180remoting.It2MeHostFacade.prototype.disconnect = function() { 181 if (this.port_) 182 this.port_.postMessage({type: 'disconnect'}); 183}; 184 185/** 186 * @return {boolean} 187 */ 188remoting.It2MeHostFacade.prototype.initialized = function() { 189 return this.initialized_; 190}; 191 192/** 193 * @return {string} 194 */ 195remoting.It2MeHostFacade.prototype.getAccessCode = function() { 196 return this.accessCode_; 197}; 198 199/** 200 * @return {number} 201 */ 202remoting.It2MeHostFacade.prototype.getAccessCodeLifetime = function() { 203 return this.accessCodeLifetime_; 204}; 205 206/** 207 * @return {string} 208 */ 209remoting.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 */ 220remoting.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 */ 297remoting.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 */ 308remoting.It2MeHostFacade.prototype.onConnected_ = function(clientId) { 309 this.clientId_ = clientId; 310}; 311 312/** 313 * @return {void} 314 * @private 315 */ 316remoting.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