// 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.HostIt2MeNativeMessaging = function() { /** * @type {number} * @private */ this.nextId_ = 0; /** * @type {?chrome.extension.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.onHostStarted_ = function() {}; /** * Called if Native Messaging host has failed to start. * @param {remoting.Error} error * @private * */ this.onHostInitFailed_ = function(error) {}; /** * 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, onHostInitFailed is invoked. * Otherwise, onHostStarted is invoked. * * @param {function():void} onHostStarted Called after successful * initialization. * @param {function(remoting.Error):void} onHostInitFailed Called if cannot * connect to host. * @param {function(remoting.Error):void} onError Called on host error after * successfully connecting to the host. * @return {void} */ remoting.HostIt2MeNativeMessaging.prototype.initialize = function(onHostStarted, onHostInitFailed, onError) { this.onHostStarted_ = onHostStarted; this.onHostInitFailed_ = onHostInitFailed; this.onError_ = onError; 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.postMessage_({type: 'hello'}); } catch (err) { console.log('Native Messaging initialization failed: ', /** @type {*} */ (err)); onHostInitFailed(remoting.Error.UNEXPECTED); return; } }; /** * Attaches a new ID to the supplied message, and posts it to the Native * Messaging port. * |message| should have its 'type' field set, and any other fields set * depending on the message type. * * @param {{type: string}} message The message to post. * @return {void} * @private */ remoting.HostIt2MeNativeMessaging.prototype.postMessage_ = function(message) { var id = this.nextId_++; message['id'] = id; this.port_.postMessage(message); }; /** * Handler for incoming Native Messages. * * @param {Object} message The received message. * @return {void} * @private */ remoting.HostIt2MeNativeMessaging.prototype.onIncomingMessage_ = function(message) { /** @type {string} */ var type = message['type']; if (!checkType_('type', type, 'string')) { this.onError_(remoting.Error.UNEXPECTED); return; } switch (type) { case 'helloResponse': /** @type {string} */ var version = message['version']; if (checkType_('version', version, 'string')) { 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". this.onHostStarted_(); } else { this.onError_(remoting.Error.UNEXPECTED); } break; case 'connectResponse': console.log('connectResponse received'); // Response to the "connect" request. No action is needed until we // receive the corresponding "hostStateChagned" message. break; case 'disconnectResponse': console.log('disconnectResponse received'); // Response to the "disconnect" request. No action is needed until we // receive the corresponding "hostStateChagned" message. break; case 'hostStateChanged': /** @type {string} */ var stateString = message['state']; if (!checkType_('stateString', stateString, 'string')) { this.onError_(remoting.Error.UNEXPECTED); break; } console.log('hostStateChanged received: ', stateString); /** @type {remoting.HostSession.State} */ var state = remoting.HostSession.stateFromString(stateString); switch (state) { case remoting.HostSession.State.RECEIVED_ACCESS_CODE: /** @type {string} */ var accessCode = message['accessCode']; if (!checkType_('accessCode', accessCode, 'string')) { this.onError_(remoting.Error.UNEXPECTED); break; } /** @type {number} */ var accessCodeLifetime = message['accessCodeLifetime']; if (!checkType_('accessCodeLifetime', accessCodeLifetime, 'number')) { this.onError_(remoting.Error.UNEXPECTED); break; } this.onReceivedAccessCode_(accessCode, accessCodeLifetime); break; case remoting.HostSession.State.CONNECTED: /** @type {string} */ var client = message['client']; if (checkType_('client', client, 'string')) { this.onConnected_(client); } else { this.onError_(remoting.Error.UNEXPECTED); } break; } this.onStateChanged_(state); break; case 'natPolicyChanged': /** @type {boolean} */ var natTraversalEnabled = message['natTraversalEnabled']; if (checkType_('natTraversalEnabled', natTraversalEnabled, 'boolean')) { this.onNatPolicyChanged_(natTraversalEnabled); } else { this.onError_(remoting.Error.UNEXPECTED); } break; case 'error': /** @type {string} */ var description = message['description']; // Ignore the return value. checkType_('description', description, 'string'); this.onError_(remoting.Error.UNEXPECTED); break; default: console.error('Unexpected native message: ', message); this.onError_(remoting.Error.UNEXPECTED); } }; /** * @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 {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. * @return {void} */ remoting.HostIt2MeNativeMessaging.prototype.connect = function(email, authServiceWithToken, onStateChanged, onNatPolicyChanged, xmppServerAddress, xmppServerUseTls, directoryBotJid) { this.onStateChanged_ = onStateChanged; this.onNatPolicyChanged_ = onNatPolicyChanged; this.postMessage_({ type: 'connect', userName: email, authServiceWithToken: authServiceWithToken, xmppServerAddress: xmppServerAddress, xmppServerUseTls: xmppServerUseTls, directoryBotJid: directoryBotJid}); }; /** * @return {void} */ remoting.HostIt2MeNativeMessaging.prototype.disconnect = function() { this.postMessage_({ type: 'disconnect'}); }; /** * @param {string} accessCode * @param {number} accessCodeLifetime * @return {void} * @private */ remoting.HostIt2MeNativeMessaging.prototype.onReceivedAccessCode_ = function(accessCode, accessCodeLifetime) { this.accessCode_ = accessCode; this.accessCodeLifetime_ = accessCodeLifetime; }; /** * @param {string} clientId * @return {void} * @private */ remoting.HostIt2MeNativeMessaging.prototype.onConnected_ = function(clientId) { this.clientId_ = clientId; }; /** * @return {void} * @private */ remoting.HostIt2MeNativeMessaging.prototype.onHostDisconnect_ = function() { if (!this.initialized_) { var error = (chrome.runtime.lastError.message == remoting.NATIVE_MESSAGING_HOST_NOT_FOUND_ERROR) ? remoting.Error.MISSING_PLUGIN : remoting.Error.UNEXPECTED; console.error('Native Messaging initialization failed.'); this.onHostInitFailed_(error); } else { console.error('Native Messaging port disconnected.'); this.onError_(remoting.Error.UNEXPECTED); } } /** * @return {string} */ remoting.HostIt2MeNativeMessaging.prototype.getAccessCode = function() { return this.accessCode_ }; /** * @return {number} */ remoting.HostIt2MeNativeMessaging.prototype.getAccessCodeLifetime = function() { return this.accessCodeLifetime_ }; /** * @return {string} */ remoting.HostIt2MeNativeMessaging.prototype.getClient = function() { return this.clientId_; };