Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / webapp / background / it2me_helper_channel.js
blobe039783273b6c462e57bc24fde01b1cc0f73b481
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.
5 /**
6  * @fileoverview
7  *
8  * It2MeHelperChannel relays messages between Hangouts and Chrome Remote Desktop
9  * (webapp) for the helper (the Hangouts participant who is giving remote
10  * assistance).
11  *
12  * It runs in the background page and contains two chrome.runtime.Port objects,
13  * representing connections to the webapp and hangout, respectively.
14  *
15  * Connection is always initiated from Hangouts by calling
16  *   var port = chrome.runtime.connect({name:'it2me.helper.hangout'}, extId).
17  *   port.postMessage('hello')
18  * If the webapp is not installed, |port.onDisconnect| will fire.
19  * If the webapp is installed, Hangouts will receive a hello response with the
20  * list of supported features.
21  *
22  *   Hangout                     It2MeHelperChannel        Chrome Remote Desktop
23  *      |-----runtime.connect() ------>|                                |
24  *      |--------hello message-------->|                                |
25  *      |                              |<-----helloResponse message-----|
26  *      |-------connect message------->|                                |
27  *      |                              |-------appLauncher.launch()---->|
28  *      |                              |<------runtime.connect()------- |
29  *      |                              |<-----sessionStateChanged------ |
30  *      |<----sessionStateChanged------|                                |
31  *
32  * Disconnection can be initiated from either side:
33  * 1. In the normal flow initiated from hangout
34  *    Hangout                    It2MeHelperChannel        Chrome Remote Desktop
35  *       |-----disconnect message------>|                               |
36  *       |<-sessionStateChanged(CLOSED)-|                               |
37  *       |                              |-----appLauncher.close()------>|
38  *
39  * 2. In the normal flow initiated from webapp
40  *    Hangout                    It2MeHelperChannel        Chrome Remote Desktop
41  *       |                              |<-sessionStateChanged(CLOSED)--|
42  *       |                              |<--------port.disconnect()-----|
43  *       |<--------port.disconnect()----|                               |
44  *
45  * 2. If hangout crashes
46  *    Hangout                    It2MeHelperChannel        Chrome Remote Desktop
47  *       |---------port.disconnect()--->|                               |
48  *       |                              |--------port.disconnect()----->|
49  *       |                              |------appLauncher.close()----->|
50  *
51  * 3. If webapp crashes
52  *    Hangout                    It2MeHelperChannel        Chrome Remote Desktop
53  *       |                              |<-------port.disconnect()------|
54  *       |<-sessionStateChanged(FAILED)-|                               |
55  *       |<--------port.disconnect()----|                               |
56  */
58 'use strict';
60 /** @suppress {duplicate} */
61 var remoting = remoting || {};
63 /**
64  * @param {remoting.AppLauncher} appLauncher
65  * @param {chrome.runtime.Port} hangoutPort Represents an active connection to
66  *     Hangouts.
67  * @param {function(remoting.It2MeHelperChannel)} onDisconnectCallback Callback
68  *     to notify when the connection is torn down.  IT2MeService uses this
69  *     callback to dispose of the channel object.
70  * @constructor
71  */
72 remoting.It2MeHelperChannel =
73     function(appLauncher, hangoutPort, onDisconnectCallback) {
75   /**
76    * @type {remoting.AppLauncher}
77    * @private
78    */
79   this.appLauncher_ = appLauncher;
81   /**
82    * @type {chrome.runtime.Port}
83    * @private
84    */
85   this.hangoutPort_ = hangoutPort;
87   /**
88    * @type {chrome.runtime.Port}
89    * @private
90    */
91   this.webappPort_ = null;
93   /**
94    * @type {string}
95    * @private
96    */
97   this.instanceId_ = '';
99   /**
100    * @type {remoting.ClientSession.State}
101    * @private
102    */
103   this.sessionState_ = remoting.ClientSession.State.CONNECTING;
105   /**
106    * @type {?function(remoting.It2MeHelperChannel)}
107    * @private
108    */
109   this.onDisconnectCallback_ = onDisconnectCallback;
111   this.onWebappMessageRef_ = this.onWebappMessage_.bind(this);
112   this.onWebappDisconnectRef_ = this.onWebappDisconnect_.bind(this);
113   this.onHangoutMessageRef_ = this.onHangoutMessage_.bind(this);
114   this.onHangoutDisconnectRef_ = this.onHangoutDisconnect_.bind(this);
117 /** @enum {string} */
118 remoting.It2MeHelperChannel.HangoutMessageTypes = {
119   HELLO: 'hello',
120   HELLO_RESPONSE: 'helloResponse',
121   CONNECT: 'connect',
122   DISCONNECT: 'disconnect'
125 /** @enum {string} */
126 remoting.It2MeHelperChannel.Features = {
127   REMOTE_ASSISTANCE: 'remoteAssistance'
130 /** @enum {string} */
131 remoting.It2MeHelperChannel.WebappMessageTypes = {
132   SESSION_STATE_CHANGED: 'sessionStateChanged'
135 remoting.It2MeHelperChannel.prototype.init = function() {
136   this.hangoutPort_.onMessage.addListener(this.onHangoutMessageRef_);
137   this.hangoutPort_.onDisconnect.addListener(this.onHangoutDisconnectRef_);
140 /** @return {string} */
141 remoting.It2MeHelperChannel.prototype.instanceId = function() {
142   return this.instanceId_;
146  * @param {{method:string, data:Object.<string,*>}} message
147  * @return {boolean} whether the message is handled or not.
148  * @private
149  */
150 remoting.It2MeHelperChannel.prototype.onHangoutMessage_ = function(message) {
151   try {
152     var MessageTypes = remoting.It2MeHelperChannel.HangoutMessageTypes;
153     switch (message.method) {
154       case MessageTypes.CONNECT:
155         this.launchWebapp_(message);
156         return true;
157       case MessageTypes.DISCONNECT:
158         this.closeWebapp_(message);
159         return true;
160       case MessageTypes.HELLO:
161         this.hangoutPort_.postMessage({
162           method: MessageTypes.HELLO_RESPONSE,
163           supportedFeatures: base.values(remoting.It2MeHelperChannel.Features)
164         });
165         return true;
166     }
167     throw new Error('Unknown message method=' + message.method);
168   } catch(e) {
169     var error = /** @type {Error} */ e;
170     console.error(error);
171     this.hangoutPort_.postMessage({
172       method: message.method + 'Response',
173       error: error.message
174     });
175   }
176   return false;
180  * Disconnect the existing connection to the helpee.
182  * @param {{method:string, data:Object.<string,*>}} message
183  * @private
184  */
185 remoting.It2MeHelperChannel.prototype.closeWebapp_ =
186     function(message) {
187   // TODO(kelvinp): Closing the v2 app currently doesn't disconnect the IT2me
188   // session (crbug.com/402137), so send an explicit notification to Hangouts.
189   this.sessionState_ = remoting.ClientSession.State.CLOSED;
190   this.hangoutPort_.postMessage({
191     method: 'sessionStateChanged',
192     state: this.sessionState_
193   });
194   this.appLauncher_.close(this.instanceId_);
198  * Launches the web app.
200  * @param {{method:string, data:Object.<string,*>}} message
201  * @private
202  */
203 remoting.It2MeHelperChannel.prototype.launchWebapp_ =
204     function(message) {
205   var accessCode = getStringAttr(message, 'accessCode');
206   if (!accessCode) {
207     throw new Error('Access code is missing');
208   }
210   // Launch the webapp.
211   this.appLauncher_.launch({
212     mode: 'hangout',
213     accessCode: accessCode
214   }).then(
215     /**
216      * @this {remoting.It2MeHelperChannel}
217      * @param {string} instanceId
218      */
219     function(instanceId){
220       this.instanceId_ = instanceId;
221     }.bind(this));
225  * @private
226  */
227 remoting.It2MeHelperChannel.prototype.onHangoutDisconnect_ = function() {
228   this.appLauncher_.close(this.instanceId_);
229   this.unhookPorts_();
233  * @param {chrome.runtime.Port} port The port represents a connection to the
234  *     webapp.
235  * @param {string} id The id of the tab or window that is hosting the webapp.
236  */
237 remoting.It2MeHelperChannel.prototype.onWebappConnect = function(port, id) {
238   base.debug.assert(id === this.instanceId_);
239   base.debug.assert(this.hangoutPort_ !== null);
241   // Hook listeners.
242   port.onMessage.addListener(this.onWebappMessageRef_);
243   port.onDisconnect.addListener(this.onWebappDisconnectRef_);
244   this.webappPort_ = port;
247 /** @param {chrome.runtime.Port} port The webapp port. */
248 remoting.It2MeHelperChannel.prototype.onWebappDisconnect_ = function(port) {
249   // If the webapp port got disconnected while the session is still connected,
250   // treat it as an error.
251   var States = remoting.ClientSession.State;
252   if (this.sessionState_ === States.CONNECTING ||
253       this.sessionState_ === States.CONNECTED) {
254     this.sessionState_ = States.FAILED;
255     this.hangoutPort_.postMessage({
256       method: 'sessionStateChanged',
257       state: this.sessionState_
258     });
259   }
260   this.unhookPorts_();
264  * @param {{method:string, data:Object.<string,*>}} message
265  * @private
266  */
267 remoting.It2MeHelperChannel.prototype.onWebappMessage_ = function(message) {
268   try {
269     console.log('It2MeHelperChannel id=' + this.instanceId_ +
270                 ' incoming message method=' + message.method);
271     var MessageTypes = remoting.It2MeHelperChannel.WebappMessageTypes;
272     switch (message.method) {
273       case MessageTypes.SESSION_STATE_CHANGED:
274         var state = getNumberAttr(message, 'state');
275         this.sessionState_ =
276             /** @type {remoting.ClientSession.State} */ state;
277         this.hangoutPort_.postMessage(message);
278         return true;
279     }
280     throw new Error('Unknown message method=' + message.method);
281   } catch(e) {
282     var error = /** @type {Error} */ e;
283     console.error(error);
284     this.webappPort_.postMessage({
285       method: message.method + 'Response',
286       error: error.message
287     });
288   }
289   return false;
292 remoting.It2MeHelperChannel.prototype.unhookPorts_ = function() {
293   if (this.webappPort_) {
294     this.webappPort_.onMessage.removeListener(this.onWebappMessageRef_);
295     this.webappPort_.onDisconnect.removeListener(this.onWebappDisconnectRef_);
296     this.webappPort_.disconnect();
297     this.webappPort_ = null;
298   }
300   if (this.hangoutPort_) {
301     this.hangoutPort_.onMessage.removeListener(this.onHangoutMessageRef_);
302     this.hangoutPort_.onDisconnect.removeListener(this.onHangoutDisconnectRef_);
303     this.hangoutPort_.disconnect();
304     this.hangoutPort_ = null;
305   }
307   if (this.onDisconnectCallback_) {
308     this.onDisconnectCallback_(this);
309     this.onDisconnectCallback_  = null;
310   }