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);
169 } catch(/** @type {*} */ e) {
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');
208 * @this {remoting.It2MeHelperChannel}
209 * @param {string} instanceId
211 var setInstance = function(instanceId) {
212 this.instanceId_ = instanceId;
215 // Launch the webapp.
216 this.appLauncher_.launch({
218 accessCode: accessCode
219 }).then(setInstance.bind(this));
225 remoting.It2MeHelperChannel.prototype.onHangoutDisconnect_ = function() {
226 this.appLauncher_.close(this.instanceId_);
231 * @param {chrome.runtime.Port} port The port represents a connection to the
233 * @param {string} id The id of the tab or window that is hosting the webapp.
235 remoting.It2MeHelperChannel.prototype.onWebappConnect = function(port, id) {
236 base.debug.assert(id === this.instanceId_);
237 base.debug.assert(this.hangoutPort_ !== null);
240 port.onMessage.addListener(this.onWebappMessageRef_);
241 port.onDisconnect.addListener(this.onWebappDisconnectRef_);
242 this.webappPort_ = port;
245 /** @param {chrome.runtime.Port} port The webapp port. */
246 remoting.It2MeHelperChannel.prototype.onWebappDisconnect_ = function(port) {
247 // If the webapp port got disconnected while the session is still connected,
248 // treat it as an error.
249 var States = remoting.ClientSession.State;
250 if (this.sessionState_ === States.CONNECTING ||
251 this.sessionState_ === States.CONNECTED) {
252 this.sessionState_ = States.FAILED;
253 this.hangoutPort_.postMessage({
254 method: 'sessionStateChanged',
255 state: this.sessionState_
262 * @param {{method:string, data:Object<string,*>}} message
265 remoting.It2MeHelperChannel.prototype.onWebappMessage_ = function(message) {
267 console.log('It2MeHelperChannel id=' + this.instanceId_ +
268 ' incoming message method=' + message.method);
269 var MessageTypes = remoting.It2MeHelperChannel.WebappMessageTypes;
270 switch (message.method) {
271 case MessageTypes.SESSION_STATE_CHANGED:
272 var state = getNumberAttr(message, 'state');
274 /** @type {remoting.ClientSession.State} */(state);
275 this.hangoutPort_.postMessage(message);
278 throw new Error('Unknown message method=' + message.method);
279 } catch(/** @type {*} */ e) {
280 var error = /** @type {Error} */ (e);
281 this.sendErrorResponse_(this.webappPort_, error, message);
286 remoting.It2MeHelperChannel.prototype.unhookPorts_ = function() {
287 if (this.webappPort_) {
288 this.webappPort_.onMessage.removeListener(this.onWebappMessageRef_);
289 this.webappPort_.onDisconnect.removeListener(this.onWebappDisconnectRef_);
290 this.webappPort_.disconnect();
291 this.webappPort_ = null;
294 if (this.hangoutPort_) {
295 this.hangoutPort_.onMessage.removeListener(this.onHangoutMessageRef_);
296 this.hangoutPort_.onDisconnect.removeListener(this.onHangoutDisconnectRef_);
297 this.hangoutPort_.disconnect();
298 this.hangoutPort_ = null;
301 if (this.onDisconnectCallback_) {
302 this.onDisconnectCallback_(this);
303 this.onDisconnectCallback_ = null;
308 * @param {chrome.runtime.Port} port
309 * @param {string|Error} error
310 * @param {?{method:string, data:Object<string,*>}=} opt_incomingMessage
313 remoting.It2MeHelperChannel.prototype.sendErrorResponse_ =
314 function(port, error, opt_incomingMessage) {
315 if (error instanceof Error) {
316 error = error.message;
319 console.error('Error responding to message method:' +
320 (opt_incomingMessage ? opt_incomingMessage.method : 'null') +
323 method: remoting.It2MeHelperChannel.HangoutMessageTypes.ERROR,
325 request: opt_incomingMessage