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.
8 * It2MeHelperChannel relays messages between Hangouts and Chrome Remote Desktop
9 * (webapp) for the helper (the Hangouts participant who is giving remote
12 * It runs in the background page and contains two chrome.runtime.Port objects,
13 * representing connections to the webapp and hangout, respectively.
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.
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------| |
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()------>|
39 * 2. In the normal flow initiated from webapp
40 * Hangout It2MeHelperChannel Chrome Remote Desktop
41 * | |<-sessionStateChanged(CLOSED)--|
42 * | |<--------port.disconnect()-----|
43 * |<--------port.disconnect()----| |
45 * 2. If hangout crashes
46 * Hangout It2MeHelperChannel Chrome Remote Desktop
47 * |---------port.disconnect()--->| |
48 * | |--------port.disconnect()----->|
49 * | |------appLauncher.close()----->|
51 * 3. If webapp crashes
52 * Hangout It2MeHelperChannel Chrome Remote Desktop
53 * | |<-------port.disconnect()------|
54 * |<-sessionStateChanged(FAILED)-| |
55 * |<--------port.disconnect()----| |
60 /** @suppress {duplicate} */
61 var remoting
= remoting
|| {};
64 * @param {remoting.AppLauncher} appLauncher
65 * @param {chrome.runtime.Port} hangoutPort Represents an active connection to
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.
72 remoting
.It2MeHelperChannel
=
73 function(appLauncher
, hangoutPort
, onDisconnectCallback
) {
76 * @type {remoting.AppLauncher}
79 this.appLauncher_
= appLauncher
;
82 * @type {chrome.runtime.Port}
85 this.hangoutPort_
= hangoutPort
;
88 * @type {chrome.runtime.Port}
91 this.webappPort_
= null;
97 this.instanceId_
= '';
100 * @type {remoting.ClientSession.State}
103 this.sessionState_
= remoting
.ClientSession
.State
.CONNECTING
;
106 * @type {?function(remoting.It2MeHelperChannel)}
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
= {
120 HELLO_RESPONSE
: 'helloResponse',
122 DISCONNECT
: 'disconnect',
126 /** @enum {string} */
127 remoting
.It2MeHelperChannel
.Features
= {
128 REMOTE_ASSISTANCE
: 'remoteAssistance'
131 /** @enum {string} */
132 remoting
.It2MeHelperChannel
.WebappMessageTypes
= {
133 SESSION_STATE_CHANGED
: 'sessionStateChanged'
136 remoting
.It2MeHelperChannel
.prototype.init = function() {
137 this.hangoutPort_
.onMessage
.addListener(this.onHangoutMessageRef_
);
138 this.hangoutPort_
.onDisconnect
.addListener(this.onHangoutDisconnectRef_
);
141 /** @return {string} */
142 remoting
.It2MeHelperChannel
.prototype.instanceId = function() {
143 return this.instanceId_
;
147 * @param {{method:string, data:Object.<string,*>}} message
148 * @return {boolean} whether the message is handled or not.
151 remoting
.It2MeHelperChannel
.prototype.onHangoutMessage_ = function(message
) {
153 var MessageTypes
= remoting
.It2MeHelperChannel
.HangoutMessageTypes
;
154 switch (message
.method
) {
155 case MessageTypes
.CONNECT
:
156 this.launchWebapp_(message
);
158 case MessageTypes
.DISCONNECT
:
159 this.closeWebapp_(message
);
161 case MessageTypes
.HELLO
:
162 this.hangoutPort_
.postMessage({
163 method
: MessageTypes
.HELLO_RESPONSE
,
164 supportedFeatures
: base
.values(remoting
.It2MeHelperChannel
.Features
)
168 throw new Error('Unknown message method=' + message
.method
);
170 var error
= /** @type {Error} */ e
;
171 this.sendErrorResponse_(this.hangoutPort_
, error
, message
);
177 * Disconnect the existing connection to the helpee.
179 * @param {{method:string, data:Object.<string,*>}} message
182 remoting
.It2MeHelperChannel
.prototype.closeWebapp_
=
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_
191 this.appLauncher_
.close(this.instanceId_
);
195 * Launches the web app.
197 * @param {{method:string, data:Object.<string,*>}} message
200 remoting
.It2MeHelperChannel
.prototype.launchWebapp_
=
202 var accessCode
= getStringAttr(message
, 'accessCode');
204 throw new Error('Access code is missing');
207 // Launch the webapp.
208 this.appLauncher_
.launch({
210 accessCode
: accessCode
213 * @this {remoting.It2MeHelperChannel}
214 * @param {string} instanceId
216 function(instanceId
){
217 this.instanceId_
= instanceId
;
224 remoting
.It2MeHelperChannel
.prototype.onHangoutDisconnect_ = function() {
225 this.appLauncher_
.close(this.instanceId_
);
230 * @param {chrome.runtime.Port} port The port represents a connection to the
232 * @param {string} id The id of the tab or window that is hosting the webapp.
234 remoting
.It2MeHelperChannel
.prototype.onWebappConnect = function(port
, id
) {
235 base
.debug
.assert(id
=== this.instanceId_
);
236 base
.debug
.assert(this.hangoutPort_
!== null);
239 port
.onMessage
.addListener(this.onWebappMessageRef_
);
240 port
.onDisconnect
.addListener(this.onWebappDisconnectRef_
);
241 this.webappPort_
= port
;
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_
261 * @param {{method:string, data:Object.<string,*>}} message
264 remoting
.It2MeHelperChannel
.prototype.onWebappMessage_ = function(message
) {
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');
273 /** @type {remoting.ClientSession.State} */ state
;
274 this.hangoutPort_
.postMessage(message
);
277 throw new Error('Unknown message method=' + message
.method
);
279 var error
= /** @type {Error} */ e
;
280 this.sendErrorResponse_(this.webappPort_
, error
, message
);
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;
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;
300 if (this.onDisconnectCallback_
) {
301 this.onDisconnectCallback_(this);
302 this.onDisconnectCallback_
= null;
307 * @param {chrome.runtime.Port} port
308 * @param {string|Error} error
309 * @param {?{method:string, data:Object.<string,*>}=} opt_incomingMessage
312 remoting
.It2MeHelperChannel
.prototype.sendErrorResponse_
=
313 function(port
, error
, opt_incomingMessage
) {
314 if (error
instanceof Error
) {
315 error
= error
.message
;
318 console
.error('Error responding to message method:' +
319 (opt_incomingMessage
? opt_incomingMessage
.method
: 'null') +
322 method
: remoting
.It2MeHelperChannel
.HangoutMessageTypes
.ERROR
,
324 request
: opt_incomingMessage