blob: 3d2c02943439140dc38e66f2335e26148ddb3fcd [file] [log] [blame]
// 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.
*/
/** @suppress {duplicate} */
var remoting = remoting || {};
(function() {
'use strict';
/**
* @constructor
* @implements {base.Disposable}
*/
remoting.It2MeHostFacade = function() {
/** @private {number} */
this.nextId_ = 0;
/** @private {?Port} */
this.port_ = null;
/** @private {string} */
this.accessCode_ = '';
/** @private {number} */
this.accessCodeLifetime_ = 0;
/** @private {string} */
this.clientId_ = '';
/** @private {boolean} */
this.initialized_ = false;
/** @private {base.Disposables} */
this.eventHooks_ = null;
/** @private {?function():void} */
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.
* @private {?function(!remoting.Error):void}
*/
this.onError_ = function(error) {};
/** @private {?function(remoting.HostSession.State):void} */
this.onStateChanged_ = function() {};
/** @private {?function(boolean):void} */
this.onNatPolicyChanged_ = function() {};
/** @private */
this.hostVersion_ = '';
/** @private */
this.debugMessageHandler_ =
new remoting.NativeMessageHostDebugMessageHandler();
};
remoting.It2MeHostFacade.prototype.dispose = function() {
base.dispose(this.eventHooks_);
this.eventHooks_ = null;
if (this.port_) {
this.port_.disconnect();
this.port_ = null;
}
};
/**
* 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.eventHooks_ = new base.Disposables(
new base.ChromeEventHook(this.port_.onMessage,
this.onIncomingMessage_.bind(this)),
new base.ChromeEventHook(this.port_.onDisconnect,
this.onHostDisconnect_.bind(this)));
this.port_.postMessage({type: 'hello'});
} catch (/** @type {*} */ err) {
console.log('Native Messaging initialization failed: ', 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 {Object} iceConfig ICE config for the host.
* @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, iceConfig, 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,
iceConfig: iceConfig
});
};
/**
* 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_;
};
/**
* @return {string}
*/
remoting.It2MeHostFacade.prototype.getHostVersion = function() {
return this.hostVersion_;
};
/**
* Handler for incoming messages.
*
* @param {Object} message The received message.
* @return {void}
* @private
*/
remoting.It2MeHostFacade.prototype.onIncomingMessage_ =
function(message) {
if (this.debugMessageHandler_.handleMessage(message)) {
return;
}
var type = base.getStringAttr(message, 'type');
switch (type) {
case 'helloResponse':
this.hostVersion_ = base.getStringAttr(message, 'version');
console.log('Host version: ', this.hostVersion_);
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 = base.getStringAttr(message, 'state');
var errorMessage = base.getStringAttr(message, 'error_message', '');
console.log('hostStateChanged received: ' + stateString);
var state = remoting.HostSession.State.fromString(stateString);
switch (state) {
case remoting.HostSession.State.RECEIVED_ACCESS_CODE:
var accessCode = base.getStringAttr(message, 'accessCode');
var accessCodeLifetime =
base.getNumberAttr(message, 'accessCodeLifetime');
this.onReceivedAccessCode_(accessCode, accessCodeLifetime);
break;
case remoting.HostSession.State.CONNECTED:
var client = base.getStringAttr(message, 'client');
this.onConnected_(client);
break;
}
if (this.onStateChanged_) {
this.onStateChanged_(state);
}
break;
case 'natPolicyChanged':
if (this.onNatPolicyChanged_) {
var natTraversalEnabled =
base.getBooleanAttr(message, 'natTraversalEnabled');
this.onNatPolicyChanged_(natTraversalEnabled);
}
break;
case 'policyError':
if (this.onError_) {
this.onError_(new remoting.Error(remoting.Error.Tag.POLICY_ERROR));
}
break;
case 'error':
console.error(base.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());
}
};
})();