Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / remoting / webapp / background / it2me_helper_channel.js
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.
4
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  */
57
58 'use strict';
59
60 /** @suppress {duplicate} */
61 var remoting = remoting || {};
62
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) {
74
75   /**
76    * @type {remoting.AppLauncher}
77    * @private
78    */
79   this.appLauncher_ = appLauncher;
80
81   /**
82    * @type {chrome.runtime.Port}
83    * @private
84    */
85   this.hangoutPort_ = hangoutPort;
86
87   /**
88    * @type {chrome.runtime.Port}
89    * @private
90    */
91   this.webappPort_ = null;
92
93   /**
94    * @type {string}
95    * @private
96    */
97   this.instanceId_ = '';
98
99   /**
100    * @type {remoting.ClientSession.State}
101    * @private
102    */
103   this.sessionState_ = remoting.ClientSession.State.CONNECTING;
104
105   /**
106    * @type {?function(remoting.It2MeHelperChannel)}
107    * @private
108    */
109   this.onDisconnectCallback_ = onDisconnectCallback;
110
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);
115 };
116
117 /** @enum {string} */
118 remoting.It2MeHelperChannel.HangoutMessageTypes = {
119   HELLO: 'hello',
120   HELLO_RESPONSE: 'helloResponse',
121   CONNECT: 'connect',
122   DISCONNECT: 'disconnect',
123   ERROR: 'error'
124 };
125
126 /** @enum {string} */
127 remoting.It2MeHelperChannel.Features = {
128   REMOTE_ASSISTANCE: 'remoteAssistance'
129 };
130
131 /** @enum {string} */
132 remoting.It2MeHelperChannel.WebappMessageTypes = {
133   SESSION_STATE_CHANGED: 'sessionStateChanged'
134 };
135
136 remoting.It2MeHelperChannel.prototype.init = function() {
137   this.hangoutPort_.onMessage.addListener(this.onHangoutMessageRef_);
138   this.hangoutPort_.onDisconnect.addListener(this.onHangoutDisconnectRef_);
139 };
140
141 /** @return {string} */
142 remoting.It2MeHelperChannel.prototype.instanceId = function() {
143   return this.instanceId_;
144 };
145
146 /**
147  * @param {{method:string, data:Object.<string,*>}} message
148  * @return {boolean} whether the message is handled or not.
149  * @private
150  */
151 remoting.It2MeHelperChannel.prototype.onHangoutMessage_ = function(message) {
152   try {
153     var MessageTypes = remoting.It2MeHelperChannel.HangoutMessageTypes;
154     switch (message.method) {
155       case MessageTypes.CONNECT:
156         this.launchWebapp_(message);
157         return true;
158       case MessageTypes.DISCONNECT:
159         this.closeWebapp_(message);
160         return true;
161       case MessageTypes.HELLO:
162         this.hangoutPort_.postMessage({
163           method: MessageTypes.HELLO_RESPONSE,
164           supportedFeatures: base.values(remoting.It2MeHelperChannel.Features)
165         });
166         return true;
167     }
168     throw new Error('Unknown message method=' + message.method);
169   } catch(e) {
170     var error = /** @type {Error} */ e;
171     this.sendErrorResponse_(this.hangoutPort_, error, message);
172   }
173   return false;
174 };
175
176 /**
177  * Disconnect the existing connection to the helpee.
178  *
179  * @param {{method:string, data:Object.<string,*>}} message
180  * @private
181  */
182 remoting.It2MeHelperChannel.prototype.closeWebapp_ =
183     function(message) {
184   // TODO(kelvinp): Closing the v2 app currently doesn't disconnect the IT2me
185   // session (crbug.com/402137), so send an explicit notification to Hangouts.
186   this.sessionState_ = remoting.ClientSession.State.CLOSED;
187   this.hangoutPort_.postMessage({
188     method: 'sessionStateChanged',
189     state: this.sessionState_
190   });
191   this.appLauncher_.close(this.instanceId_);
192 };
193
194 /**
195  * Launches the web app.
196  *
197  * @param {{method:string, data:Object.<string,*>}} message
198  * @private
199  */
200 remoting.It2MeHelperChannel.prototype.launchWebapp_ =
201     function(message) {
202   var accessCode = getStringAttr(message, 'accessCode');
203   if (!accessCode) {
204     throw new Error('Access code is missing');
205   }
206
207   // Launch the webapp.
208   this.appLauncher_.launch({
209     mode: 'hangout',
210     accessCode: accessCode
211   }).then(
212     /**
213      * @this {remoting.It2MeHelperChannel}
214      * @param {string} instanceId
215      */
216     function(instanceId){
217       this.instanceId_ = instanceId;
218     }.bind(this));
219 };
220
221 /**
222  * @private
223  */
224 remoting.It2MeHelperChannel.prototype.onHangoutDisconnect_ = function() {
225   this.appLauncher_.close(this.instanceId_);
226   this.unhookPorts_();
227 };
228
229 /**
230  * @param {chrome.runtime.Port} port The port represents a connection to the
231  *     webapp.
232  * @param {string} id The id of the tab or window that is hosting the webapp.
233  */
234 remoting.It2MeHelperChannel.prototype.onWebappConnect = function(port, id) {
235   base.debug.assert(id === this.instanceId_);
236   base.debug.assert(this.hangoutPort_ !== null);
237
238   // Hook listeners.
239   port.onMessage.addListener(this.onWebappMessageRef_);
240   port.onDisconnect.addListener(this.onWebappDisconnectRef_);
241   this.webappPort_ = port;
242 };
243
244 /** @param {chrome.runtime.Port} port The webapp port. */
245 remoting.It2MeHelperChannel.prototype.onWebappDisconnect_ = function(port) {
246   // If the webapp port got disconnected while the session is still connected,
247   // treat it as an error.
248   var States = remoting.ClientSession.State;
249   if (this.sessionState_ === States.CONNECTING ||
250       this.sessionState_ === States.CONNECTED) {
251     this.sessionState_ = States.FAILED;
252     this.hangoutPort_.postMessage({
253       method: 'sessionStateChanged',
254       state: this.sessionState_
255     });
256   }
257   this.unhookPorts_();
258 };
259
260 /**
261  * @param {{method:string, data:Object.<string,*>}} message
262  * @private
263  */
264 remoting.It2MeHelperChannel.prototype.onWebappMessage_ = function(message) {
265   try {
266     console.log('It2MeHelperChannel id=' + this.instanceId_ +
267                 ' incoming message method=' + message.method);
268     var MessageTypes = remoting.It2MeHelperChannel.WebappMessageTypes;
269     switch (message.method) {
270       case MessageTypes.SESSION_STATE_CHANGED:
271         var state = getNumberAttr(message, 'state');
272         this.sessionState_ =
273             /** @type {remoting.ClientSession.State} */ state;
274         this.hangoutPort_.postMessage(message);
275         return true;
276     }
277     throw new Error('Unknown message method=' + message.method);
278   } catch(e) {
279     var error = /** @type {Error} */ e;
280     this.sendErrorResponse_(this.webappPort_, error, message);
281   }
282   return false;
283 };
284
285 remoting.It2MeHelperChannel.prototype.unhookPorts_ = function() {
286   if (this.webappPort_) {
287     this.webappPort_.onMessage.removeListener(this.onWebappMessageRef_);
288     this.webappPort_.onDisconnect.removeListener(this.onWebappDisconnectRef_);
289     this.webappPort_.disconnect();
290     this.webappPort_ = null;
291   }
292
293   if (this.hangoutPort_) {
294     this.hangoutPort_.onMessage.removeListener(this.onHangoutMessageRef_);
295     this.hangoutPort_.onDisconnect.removeListener(this.onHangoutDisconnectRef_);
296     this.hangoutPort_.disconnect();
297     this.hangoutPort_ = null;
298   }
299
300   if (this.onDisconnectCallback_) {
301     this.onDisconnectCallback_(this);
302     this.onDisconnectCallback_  = null;
303   }
304 };
305
306 /**
307  * @param {chrome.runtime.Port} port
308  * @param {string|Error} error
309  * @param {?{method:string, data:Object.<string,*>}=} opt_incomingMessage
310  * @private
311  */
312 remoting.It2MeHelperChannel.prototype.sendErrorResponse_ =
313     function(port, error, opt_incomingMessage) {
314   if (error instanceof Error) {
315     error = error.message;
316   }
317
318   console.error('Error responding to message method:' +
319                 (opt_incomingMessage ? opt_incomingMessage.method : 'null') +
320                 ' error:' + error);
321   port.postMessage({
322     method: remoting.It2MeHelperChannel.HangoutMessageTypes.ERROR,
323     message: error,
324     request: opt_incomingMessage
325   });
326 };