16e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
26e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
36e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)// found in the LICENSE file.
46e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
56e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
66e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @fileoverview
76e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * It2MeService listens to incoming connections requests from Hangouts
86e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * and the webapp and creates a It2MeHelperChannel between them.
96e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * It supports multiple helper sessions, but only a single helpee.
106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)'use strict';
136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/** @suppress {duplicate} */
156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)var remoting = remoting || {};
166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {remoting.AppLauncher} appLauncher
196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @constructor
206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService = function(appLauncher) {
226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  /**
236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   * @type {remoting.AppLauncher}
246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   * @private
256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   */
266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.appLauncher_ = appLauncher;
276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  /**
296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   * @type {Array.<remoting.It2MeHelperChannel>}
306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   * @private
316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)   */
326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.helpers_ = [];
336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  /** @private */
356e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.helpee_ = null;
366e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.onWebappConnectRef_ = this.onWebappConnect_.bind(this);
386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.onConnectExternalRef_ = this.onConnectExternal_.bind(this);
396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/** @enum {string} */
426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.ConnectionTypes = {
436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  HELPER_HANGOUT: 'it2me.helper.hangout',
446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  HELPEE_HANGOUT: 'it2me.helpee.hangout',
456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  HELPER_WEBAPP: 'it2me.helper.webapp'
466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
476e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * Starts listening to external connection from Hangouts and the webapp.
506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.init = function() {
526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  chrome.runtime.onConnect.addListener(this.onWebappConnectRef_);
536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  chrome.runtime.onConnectExternal.addListener(this.onConnectExternalRef_);
546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.dispose = function() {
576e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  chrome.runtime.onConnect.removeListener(this.onWebappConnectRef_);
586e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  chrome.runtime.onConnectExternal.removeListener(
596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      this.onConnectExternalRef_);
606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * This function is called when Hangouts connects via chrome.runtime.connect.
646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * Only web pages that are white-listed in the manifest are allowed to connect.
656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) *
666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {chrome.runtime.Port} port
676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @private
686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.onConnectExternal_ = function(port) {
706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  var ConnectionTypes = remoting.It2MeService.ConnectionTypes;
716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  try {
726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    switch (port.name) {
736e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      case ConnectionTypes.HELPER_HANGOUT:
746e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.handleExternalHelperConnection_(port);
756e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        return true;
7603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      case ConnectionTypes.HELPEE_HANGOUT:
7703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        this.handleExternalHelpeeConnection_(port);
7803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)        return true;
796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      default:
806e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        throw new Error('Unsupported port - ' + port.name);
816e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
826e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  } catch (e) {
836e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var error = /**@type {Error} */ e;
846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    console.error(error);
856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    port.disconnect();
866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  return false;
886e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
896e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {chrome.runtime.Port} port
926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @private
936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.onWebappConnect_ = function(port) {
956e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  try {
966e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    console.log('Incoming helper connection from webapp.');
976e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
986e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    // The senderId (tabId or windowId) of the webapp is embedded in the port
996e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    // name with the format port_name@senderId.
1006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var parts = port.name.split('@');
1016e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var portName = parts[0];
1026e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var senderId = parts[1];
1036e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var ConnectionTypes = remoting.It2MeService.ConnectionTypes;
1046e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if (portName === ConnectionTypes.HELPER_WEBAPP && senderId !== undefined) {
1056e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      for (var i = 0; i < this.helpers_.length; i++) {
1066e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        var helper = this.helpers_[i];
1076e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        if (helper.instanceId() === senderId) {
1086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          helper.onWebappConnect(port, senderId);
1096e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)          return;
1106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        }
1116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      }
1126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    throw new Error('No matching hangout connection found for ' + port.name);
1146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  } catch (e) {
1156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    var error = /** @type {Error} */ e;
1166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    console.error(error);
1176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    port.disconnect();
1186e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
1196e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
1206e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1216e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
1226e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {remoting.It2MeHelperChannel} helper
1236e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
1246e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.onHelperChannelDisconnected = function(helper) {
1256e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  for (var i = 0; i < this.helpers_.length; i++) {
1266e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    if (helper === this.helpers_[i]) {
1276e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      this.helpers_.splice(i, 1);
1286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    }
1296e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
1306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
1316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
13203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)remoting.It2MeService.prototype.onHelpeeChannelDisconnected = function() {
13303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  base.debug.assert(this.helpee_ !== null);
13403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  this.helpee_ = null;
13503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)};
13603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
1376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)/**
1386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {chrome.runtime.Port} port
1396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @private
1406e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) */
1416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)remoting.It2MeService.prototype.handleExternalHelperConnection_ =
1426e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    function(port) {
1436e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  if (this.helpee_) {
1446e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    console.error(
1456e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        'Cannot start a helper session while a helpee session is in process.');
1466e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    port.disconnect();
14703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return;
1486e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  }
1496e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)
1506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  console.log('Incoming helper connection from Hangouts');
1516e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  var helper = new remoting.It2MeHelperChannel(
1526e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)      this.appLauncher_, port, this.onHelperChannelDisconnected.bind(this));
1536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  helper.init();
1546e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  this.helpers_.push(helper);
1556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)};
15603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
15703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/**
15803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {chrome.runtime.Port} hangoutPort Represents a connection to Hangouts.
15903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @private
16003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
16103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)remoting.It2MeService.prototype.handleExternalHelpeeConnection_ =
16203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    function(hangoutPort) {
16303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (this.helpee_) {
16403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    console.error('An existing helpee session is in process.');
16503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    hangoutPort.disconnect();
16603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return;
16703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
16803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
16903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  console.log('Incoming helpee connection from Hangouts');
17003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  this.helpee_ = new remoting.It2MeHelpeeChannel(
17103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      hangoutPort,
17203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      new remoting.It2MeHostFacade(),
17303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      new remoting.HostInstaller(),
17403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)      this.onHelpeeChannelDisconnected.bind(this));
17503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  this.helpee_.init();
17603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)};