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