// 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 */ remoting.It2MeHostFacade = function() { /** * @type {number} * @private */ this.nextId_ = 0; /** * @type {?chrome.runtime.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.onInitialized_ = function() {}; /** * Called if Native Messaging host has failed to start. * @private * */ this.onInitializationFailed_ = function() {}; /** * Called if the It2Me Native Messaging host sends a malformed message: * missing required attributes, attributes with incorrect types, etc. * @param {remoting.Error} error * @private */ this.onError_ = function(error) {}; /** * @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, onInitializationFailed is invoked. * Otherwise, onInitialized is invoked. * * @param {function():void} onInitialized Called after successful * initialization. * @param {function():void} onInitializationFailed Called if cannot connect to * the native messaging host. * @return {void} */ remoting.It2MeHostFacade.prototype.initialize = function(onInitialized, onInitializationFailed) { this.onInitialized_ = onInitialized; this.onInitializationFailed_ = onInitializationFailed; 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.port_.postMessage({type: 'hello'}); } catch (err) { console.log('Native Messaging initialization failed: ', /** @type {*} */ (err)); onInitializationFailed(); return; } }; /** * @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 {function(string):void} logDebugInfo Callback allowing the plugin * to log messages to the debug log. * @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(remoting.Error):void} onError Callback to invoke in case of * an error. * @return {void} */ remoting.It2MeHostFacade.prototype.connect = function(email, authServiceWithToken, onStateChanged, onNatPolicyChanged, logDebugInfo, xmppServerAddress, xmppServerUseTls, directoryBotJid, onError) { if (!this.port_) { console.error( 'remoting.It2MeHostFacade.connect() without initialization.'); onError(remoting.Error.UNEXPECTED); return; } this.onStateChanged_ = onStateChanged; this.onNatPolicyChanged_ = onNatPolicyChanged; this.onError_ = onError; this.port_.postMessage({ type: 'connect', userName: email, authServiceWithToken: authServiceWithToken, xmppServerAddress: xmppServerAddress, xmppServerUseTls: xmppServerUseTls, directoryBotJid: directoryBotJid }); }; /** * Unhooks the |onStateChanged|, |onError|, |onNatPolicyChanged| and * |onInitalized| callbacks. This is called when the client shuts down so that * the callbacks will not be invoked on a disposed client. * * @return {void} */ remoting.It2MeHostFacade.prototype.unhookCallbacks = function() { this.onStateChanged_ = null; this.onNatPolicyChanged_ = null; this.onError_ = null; this.onInitialized_ = null; }; /** * @return {void} */ remoting.It2MeHostFacade.prototype.disconnect = function() { if (this.port_) this.port_.postMessage({type: 'disconnect'}); }; /** * @return {boolean} */ remoting.It2MeHostFacade.prototype.initialized = function() { return this.initialized_; }; /** * @return {string} */ remoting.It2MeHostFacade.prototype.getAccessCode = function() { return this.accessCode_; }; /** * @return {number} */ remoting.It2MeHostFacade.prototype.getAccessCodeLifetime = function() { return this.accessCodeLifetime_; }; /** * @return {string} */ remoting.It2MeHostFacade.prototype.getClient = function() { return this.clientId_; }; /** * Handler for incoming messages. * * @param {Object} message The received message. * @return {void} * @private */ remoting.It2MeHostFacade.prototype.onIncomingMessage_ = function(message) { var type = getStringAttr(message, 'type'); switch (type) { case 'helloResponse': var version = getStringAttr(message, 'version'); 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". if (this.onInitialized_) { this.onInitialized_(); } break; case 'connectResponse': console.log('connectResponse received'); // Response to the "connect" request. No action is needed until we // receive the corresponding "hostStateChanged" message. break; case 'disconnectResponse': console.log('disconnectResponse received'); // Response to the "disconnect" request. No action is needed until we // receive the corresponding "hostStateChanged" message. break; case 'hostStateChanged': var stateString = getStringAttr(message, 'state'); console.log('hostStateChanged received: ', stateString); var state = remoting.HostSession.State.fromString(stateString); switch (state) { case remoting.HostSession.State.RECEIVED_ACCESS_CODE: var accessCode = getStringAttr(message, 'accessCode'); var accessCodeLifetime = getNumberAttr(message, 'accessCodeLifetime'); this.onReceivedAccessCode_(accessCode, accessCodeLifetime); break; case remoting.HostSession.State.CONNECTED: var client = getStringAttr(message, 'client'); this.onConnected_(client); break; } if (this.onStateChanged_) { this.onStateChanged_(state); } break; case 'natPolicyChanged': if (this.onNatPolicyChanged_) { var natTraversalEnabled = getBooleanAttr(message, 'natTraversalEnabled'); this.onNatPolicyChanged_(natTraversalEnabled); } break; case 'error': console.error(getStringAttr(message, 'description')); if (this.onError_) { this.onError_(remoting.Error.UNEXPECTED); } break; default: throw 'Unexpected native message: ' + message; } }; /** * @param {string} accessCode * @param {number} accessCodeLifetime * @return {void} * @private */ remoting.It2MeHostFacade.prototype.onReceivedAccessCode_ = function(accessCode, accessCodeLifetime) { this.accessCode_ = accessCode; this.accessCodeLifetime_ = accessCodeLifetime; }; /** * @param {string} clientId * @return {void} * @private */ remoting.It2MeHostFacade.prototype.onConnected_ = function(clientId) { this.clientId_ = clientId; }; /** * @return {void} * @private */ remoting.It2MeHostFacade.prototype.onHostDisconnect_ = function() { if (!this.initialized_) { // If the host is disconnected before it is initialized, it probably means // the host is not propertly installed (or not installed at all). // E.g., if the host manifest is not present we get "Specified native // messaging host not found" error. If the host manifest is present but // the host binary cannot be found we get the "Native host has exited" // error. console.log('Native Messaging initialization failed: ' + chrome.runtime.lastError.message); this.onInitializationFailed_(); } else { console.error('Native Messaging port disconnected.'); this.port_ = null; this.onError_(remoting.Error.UNEXPECTED); } }