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'
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.
150 remoting.It2MeHelperChannel.prototype.onHangoutMessage_ = function(message) {
152 var MessageTypes = remoting.It2MeHelperChannel.HangoutMessageTypes;
153 switch (message.method) {
154 case MessageTypes.CONNECT:
155 this.launchWebapp_(message);
157 case MessageTypes.DISCONNECT:
158 this.closeWebapp_(message);
160 case MessageTypes.HELLO:
161 this.hangoutPort_.postMessage({
162 method: MessageTypes.HELLO_RESPONSE,
163 supportedFeatures: base.values(remoting.It2MeHelperChannel.Features)
167 throw new Error('Unknown message method=' + message.method);
169 var error = /** @type {Error} */ e;
170 console.error(error);
171 this.hangoutPort_.postMessage({
172 method: message.method + 'Response',
180 * Disconnect the existing connection to the helpee.
182 * @param {{method:string, data:Object.<string,*>}} message
185 remoting.It2MeHelperChannel.prototype.closeWebapp_ =
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_
194 this.appLauncher_.close(this.instanceId_);
198 * Launches the web app.
200 * @param {{method:string, data:Object.<string,*>}} message
203 remoting.It2MeHelperChannel.prototype.launchWebapp_ =
205 var accessCode = getStringAttr(message, 'accessCode');
207 throw new Error('Access code is missing');
210 // Launch the webapp.
211 this.appLauncher_.launch({
213 accessCode: accessCode
216 * @this {remoting.It2MeHelperChannel}
217 * @param {string} instanceId
219 function(instanceId){
220 this.instanceId_ = instanceId;
227 remoting.It2MeHelperChannel.prototype.onHangoutDisconnect_ = function() {
228 this.appLauncher_.close(this.instanceId_);
233 * @param {chrome.runtime.Port} port The port represents a connection to the
235 * @param {string} id The id of the tab or window that is hosting the webapp.
237 remoting.It2MeHelperChannel.prototype.onWebappConnect = function(port, id) {
238 base.debug.assert(id === this.instanceId_);
239 base.debug.assert(this.hangoutPort_ !== null);
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_
264 * @param {{method:string, data:Object.<string,*>}} message
267 remoting.It2MeHelperChannel.prototype.onWebappMessage_ = function(message) {
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');
276 /** @type {remoting.ClientSession.State} */ state;
277 this.hangoutPort_.postMessage(message);
280 throw new Error('Unknown message method=' + message.method);
282 var error = /** @type {Error} */ e;
283 console.error(error);
284 this.webappPort_.postMessage({
285 method: message.method + 'Response',
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;
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;
307 if (this.onDisconnectCallback_) {
308 this.onDisconnectCallback_(this);
309 this.onDisconnectCallback_ = null;