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 * respresenting connections to the webapp and hangout, respectively.
15 * Connection is always initiated from Hangouts.
17 * Hangout It2MeHelperChannel Chrome Remote Desktop
18 * |-----runtime.connect() ------>| |
19 * |------connect message-------->| |
20 * | |-------appLauncher.launch()---->|
21 * | |<------runtime.connect()------- |
22 * | |<-----sessionStateChanged------ |
23 * |<----sessionStateChanged------| |
25 * Disconnection can be initiated from either side:
26 * 1. In the normal flow initiated from hangout
27 * Hangout It2MeHelperChannel Chrome Remote Desktop
28 * |-----disconnect message------>| |
29 * |<-sessionStateChanged(CLOSED)-| |
30 * | |-----appLauncher.close()------>|
32 * 2. In the normal flow initiated from webapp
33 * Hangout It2MeHelperChannel Chrome Remote Desktop
34 * | |<-sessionStateChanged(CLOSED)--|
35 * | |<--------port.disconnect()-----|
36 * |<--------port.disconnect()----| |
38 * 2. If hangout crashes
39 * Hangout It2MeHelperChannel Chrome Remote Desktop
40 * |---------port.disconnect()--->| |
41 * | |--------port.disconnect()----->|
42 * | |------appLauncher.close()----->|
44 * 3. If webapp crashes
45 * Hangout It2MeHelperChannel Chrome Remote Desktop
46 * | |<-------port.disconnect()------|
47 * |<-sessionStateChanged(FAILED)-| |
48 * |<--------port.disconnect()----| |
53 /** @suppress {duplicate} */
54 var remoting = remoting || {};
57 * @param {remoting.AppLauncher} appLauncher
58 * @param {chrome.runtime.Port} hangoutPort Represents an active connection to
60 * @param {function(remoting.It2MeHelperChannel)} onDisconnectCallback Callback
61 * to notify when the connection is torn down. IT2MeService uses this
62 * callback to dispose of the channel object.
65 remoting.It2MeHelperChannel =
66 function(appLauncher, hangoutPort, onDisconnectCallback) {
69 * @type {remoting.AppLauncher}
72 this.appLauncher_ = appLauncher;
75 * @type {chrome.runtime.Port}
78 this.hangoutPort_ = hangoutPort;
81 * @type {chrome.runtime.Port}
84 this.webappPort_ = null;
90 this.instanceId_ = '';
93 * @type {remoting.ClientSession.State}
96 this.sessionState_ = remoting.ClientSession.State.CONNECTING;
99 * @type {?function(remoting.It2MeHelperChannel)}
102 this.onDisconnectCallback_ = onDisconnectCallback;
104 this.onWebappMessageRef_ = this.onWebappMessage_.bind(this);
105 this.onWebappDisconnectRef_ = this.onWebappDisconnect_.bind(this);
106 this.onHangoutMessageRef_ = this.onHangoutMessage_.bind(this);
107 this.onHangoutDisconnectRef_ = this.onHangoutDisconnect_.bind(this);
110 /** @enum {string} */
111 remoting.It2MeHelperChannel.HangoutMessageTypes = {
113 DISCONNECT: 'disconnect'
116 /** @enum {string} */
117 remoting.It2MeHelperChannel.WebappMessageTypes = {
118 SESSION_STATE_CHANGED: 'sessionStateChanged'
121 remoting.It2MeHelperChannel.prototype.init = function() {
122 this.hangoutPort_.onMessage.addListener(this.onHangoutMessageRef_);
123 this.hangoutPort_.onDisconnect.addListener(this.onHangoutDisconnectRef_);
126 /** @return {string} */
127 remoting.It2MeHelperChannel.prototype.instanceId = function() {
128 return this.instanceId_;
132 * @param {{method:string, data:Object.<string,*>}} message
133 * @return {boolean} whether the message is handled or not.
136 remoting.It2MeHelperChannel.prototype.onHangoutMessage_ = function(message) {
138 var MessageTypes = remoting.It2MeHelperChannel.HangoutMessageTypes;
139 switch (message.method) {
140 case MessageTypes.CONNECT:
141 this.launchWebapp_(message);
143 case MessageTypes.DISCONNECT:
144 this.closeWebapp_(message);
148 var error = /** @type {Error} */ e;
149 console.error(error);
150 this.hangoutPort_.postMessage({
151 method: message.method + 'Response',
159 * Disconnect the existing connection to the helpee.
161 * @param {{method:string, data:Object.<string,*>}} message
164 remoting.It2MeHelperChannel.prototype.closeWebapp_ =
166 // TODO(kelvinp): Closing the v2 app currently doesn't disconnect the IT2me
167 // session (crbug.com/402137), so send an explicit notification to Hangouts.
168 this.sessionState_ = remoting.ClientSession.State.CLOSED;
169 this.hangoutPort_.postMessage({
170 method: 'sessionStateChanged',
171 state: this.sessionState_
173 this.appLauncher_.close(this.instanceId_);
177 * Launches the web app.
179 * @param {{method:string, data:Object.<string,*>}} message
182 remoting.It2MeHelperChannel.prototype.launchWebapp_ =
184 var accessCode = getStringAttr(message, 'accessCode');
186 throw new Error('Access code is missing');
189 // Launch the webapp.
190 this.appLauncher_.launch({
192 accessCode: accessCode
195 * @this {remoting.It2MeHelperChannel}
196 * @param {string} instanceId
198 function(instanceId){
199 this.instanceId_ = instanceId;
206 remoting.It2MeHelperChannel.prototype.onHangoutDisconnect_ = function() {
207 this.appLauncher_.close(this.instanceId_);
212 * @param {chrome.runtime.Port} port The port represents a connection to the
214 * @param {string} id The id of the tab or window that is hosting the webapp.
216 remoting.It2MeHelperChannel.prototype.onWebappConnect = function(port, id) {
217 base.debug.assert(id === this.instanceId_);
218 base.debug.assert(this.hangoutPort_ !== null);
221 port.onMessage.addListener(this.onWebappMessageRef_);
222 port.onDisconnect.addListener(this.onWebappDisconnectRef_);
223 this.webappPort_ = port;
226 /** @param {chrome.runtime.Port} port The webapp port. */
227 remoting.It2MeHelperChannel.prototype.onWebappDisconnect_ = function(port) {
228 // If the webapp port got disconnected while the session is still connected,
229 // treat it as an error.
230 var States = remoting.ClientSession.State;
231 if (this.sessionState_ === States.CONNECTING ||
232 this.sessionState_ === States.CONNECTED) {
233 this.sessionState_ = States.FAILED;
234 this.hangoutPort_.postMessage({
235 method: 'sessionStateChanged',
236 state: this.sessionState_
243 * @param {{method:string, data:Object.<string,*>}} message
246 remoting.It2MeHelperChannel.prototype.onWebappMessage_ = function(message) {
248 console.log('It2MeHelperChannel id=' + this.instanceId_ +
249 ' incoming message method=' + message.method);
250 var MessageTypes = remoting.It2MeHelperChannel.WebappMessageTypes;
251 switch (message.method) {
252 case MessageTypes.SESSION_STATE_CHANGED:
253 var state = getNumberAttr(message, 'state');
255 /** @type {remoting.ClientSession.State} */ state;
256 this.hangoutPort_.postMessage(message);
259 throw new Error('Unknown message method=' + message.method);
261 var error = /** @type {Error} */ e;
262 console.error(error);
263 this.webappPort_.postMessage({
264 method: message.method + 'Response',
271 remoting.It2MeHelperChannel.prototype.unhookPorts_ = function() {
272 if (this.webappPort_) {
273 this.webappPort_.onMessage.removeListener(this.onWebappMessageRef_);
274 this.webappPort_.onDisconnect.removeListener(this.onWebappDisconnectRef_);
275 this.webappPort_.disconnect();
276 this.webappPort_ = null;
279 if (this.hangoutPort_) {
280 this.hangoutPort_.onMessage.removeListener(this.onHangoutMessageRef_);
281 this.hangoutPort_.onDisconnect.removeListener(this.onHangoutDisconnectRef_);
282 this.hangoutPort_.disconnect();
283 this.hangoutPort_ = null;
286 if (this.onDisconnectCallback_) {
287 this.onDisconnectCallback_(this);
288 this.onDisconnectCallback_ = null;