Update V8 to version 3.30.4 (based on bleeding_edge revision r24443)
[chromium-blink-merge.git] / remoting / webapp / background / it2me_helpee_channel.js
blobb53f79858fc635d9bdff346bd190046cfc9bd857
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.
5 /**
6 * @fileoverview
8 * It2MeHelpeeChannel relays messages between the Hangouts web page (Hangouts)
9 * and the It2Me Native Messaging Host (It2MeHost) for the helpee (the Hangouts
10 * participant who is receiving remoting assistance).
12 * It runs in the background page. It contains a chrome.runtime.Port object,
13 * representing a connection to Hangouts, and a remoting.It2MeHostFacade object,
14 * representing a connection to the IT2Me Native Messaging Host.
16 * Hangouts It2MeHelpeeChannel It2MeHost
17 * |---------runtime.connect()-------->| |
18 * |-----------hello message---------->| |
19 * |<-----helloResponse message------->| |
20 * |----------connect message--------->| |
21 * | |-----showConfirmDialog()----->|
22 * | |----------connect()---------->|
23 * | |<-------hostStateChanged------|
24 * | | (RECEIVED_ACCESS_CODE) |
25 * |<---connect response (access code)-| |
26 * | | |
28 * Hangouts will send the access code to the web app on the helper side.
29 * The helper will then connect to the It2MeHost using the access code.
31 * Hangouts It2MeHelpeeChannel It2MeHost
32 * | |<-------hostStateChanged------|
33 * | | (CONNECTED) |
34 * |<-- hostStateChanged(CONNECTED)----| |
35 * |-------disconnect message--------->| |
36 * |<--hostStateChanged(DISCONNECTED)--| |
39 * It also handles host downloads and install status queries:
41 * Hangouts It2MeHelpeeChannel
42 * |------isHostInstalled message----->|
43 * |<-isHostInstalled response(false)--|
44 * | |
45 * |--------downloadHost message------>|
46 * | |
47 * |------isHostInstalled message----->|
48 * |<-isHostInstalled response(false)--|
49 * | |
50 * |------isHostInstalled message----->|
51 * |<-isHostInstalled response(true)---|
54 'use strict';
56 /** @suppress {duplicate} */
57 var remoting = remoting || {};
59 /**
60 * @param {chrome.runtime.Port} hangoutPort
61 * @param {remoting.It2MeHostFacade} host
62 * @param {remoting.HostInstaller} hostInstaller
63 * @param {function()} onDisposedCallback Callback to notify the client when
64 * the connection is torn down.
66 * @constructor
67 * @implements {base.Disposable}
69 remoting.It2MeHelpeeChannel =
70 function(hangoutPort, host, hostInstaller, onDisposedCallback) {
71 /**
72 * @type {chrome.runtime.Port}
73 * @private
75 this.hangoutPort_ = hangoutPort;
77 /**
78 * @type {remoting.It2MeHostFacade}
79 * @private
81 this.host_ = host;
83 /**
84 * @type {?remoting.HostInstaller}
85 * @private
87 this.hostInstaller_ = hostInstaller;
89 /**
90 * @type {remoting.HostSession.State}
91 * @private
93 this.hostState_ = remoting.HostSession.State.UNKNOWN;
95 /**
96 * @type {?function()}
97 * @private
99 this.onDisposedCallback_ = onDisposedCallback;
101 this.onHangoutMessageRef_ = this.onHangoutMessage_.bind(this);
102 this.onHangoutDisconnectRef_ = this.onHangoutDisconnect_.bind(this);
105 /** @enum {string} */
106 remoting.It2MeHelpeeChannel.HangoutMessageTypes = {
107 CONNECT: 'connect',
108 CONNECT_RESPONSE: 'connectResponse',
109 DISCONNECT: 'disconnect',
110 DOWNLOAD_HOST: 'downloadHost',
111 ERROR: 'error',
112 HELLO: 'hello',
113 HELLO_RESPONSE: 'helloResponse',
114 HOST_STATE_CHANGED: 'hostStateChanged',
115 IS_HOST_INSTALLED: 'isHostInstalled',
116 IS_HOST_INSTALLED_RESPONSE: 'isHostInstalledResponse'
119 /** @enum {string} */
120 remoting.It2MeHelpeeChannel.Features = {
121 REMOTE_ASSISTANCE: 'remoteAssistance'
124 remoting.It2MeHelpeeChannel.prototype.init = function() {
125 this.hangoutPort_.onMessage.addListener(this.onHangoutMessageRef_);
126 this.hangoutPort_.onDisconnect.addListener(this.onHangoutDisconnectRef_);
129 remoting.It2MeHelpeeChannel.prototype.dispose = function() {
130 if (this.host_ !== null) {
131 this.host_.unhookCallbacks();
132 this.host_.disconnect();
133 this.host_ = null;
136 if (this.hangoutPort_ !== null) {
137 this.hangoutPort_.onMessage.removeListener(this.onHangoutMessageRef_);
138 this.hangoutPort_.onDisconnect.removeListener(this.onHangoutDisconnectRef_);
139 this.hostState_ = remoting.HostSession.State.DISCONNECTED;
141 try {
142 var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
143 this.hangoutPort_.postMessage({
144 method: MessageTypes.HOST_STATE_CHANGED,
145 state: this.hostState_
147 } catch (e) {
148 // |postMessage| throws if |this.hangoutPort_| is disconnected
149 // It is safe to ignore the exception.
151 this.hangoutPort_.disconnect();
152 this.hangoutPort_ = null;
155 if (this.onDisposedCallback_ !== null) {
156 this.onDisposedCallback_();
157 this.onDisposedCallback_ = null;
162 * Message Handler for incoming runtime messages from Hangouts.
164 * @param {{method:string, data:Object.<string,*>}} message
165 * @private
167 remoting.It2MeHelpeeChannel.prototype.onHangoutMessage_ = function(message) {
168 try {
169 var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
170 switch (message.method) {
171 case MessageTypes.HELLO:
172 this.hangoutPort_.postMessage({
173 method: MessageTypes.HELLO_RESPONSE,
174 supportedFeatures: base.values(remoting.It2MeHelpeeChannel.Features)
176 return true;
177 case MessageTypes.IS_HOST_INSTALLED:
178 this.handleIsHostInstalled_(message);
179 return true;
180 case MessageTypes.DOWNLOAD_HOST:
181 this.handleDownloadHost_(message);
182 return true;
183 case MessageTypes.CONNECT:
184 this.handleConnect_(message);
185 return true;
186 case MessageTypes.DISCONNECT:
187 this.dispose();
188 return true;
190 throw new Error('Unsupported message method=' + message.method);
191 } catch(e) {
192 var error = /** @type {Error} */ e;
193 this.sendErrorResponse_(message, error.message);
195 return false;
199 * Queries the |hostInstaller| for the installation status.
201 * @param {{method:string, data:Object.<string,*>}} message
202 * @private
204 remoting.It2MeHelpeeChannel.prototype.handleIsHostInstalled_ =
205 function(message) {
206 /** @type {remoting.It2MeHelpeeChannel} */
207 var that = this;
209 /** @param {boolean} installed */
210 function sendResponse(installed) {
211 var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
212 that.hangoutPort_.postMessage({
213 method: MessageTypes.IS_HOST_INSTALLED_RESPONSE,
214 result: installed
218 this.hostInstaller_.isInstalled().then(
219 sendResponse,
220 this.sendErrorResponse_.bind(this, message)
225 * @param {{method:string, data:Object.<string,*>}} message
226 * @private
228 remoting.It2MeHelpeeChannel.prototype.handleDownloadHost_ = function(message) {
229 try {
230 this.hostInstaller_.download();
231 } catch (e) {
232 var error = /** @type {Error} */ e;
233 this.sendErrorResponse_(message, error.message);
238 * Disconnect the session if the |hangoutPort| gets disconnected.
239 * @private
241 remoting.It2MeHelpeeChannel.prototype.onHangoutDisconnect_ = function() {
242 this.dispose();
246 * Connects to the It2Me Native messaging Host and retrieves the access code.
248 * @param {{method:string, data:Object.<string,*>}} message
249 * @private
251 remoting.It2MeHelpeeChannel.prototype.handleConnect_ =
252 function(message) {
253 var email = getStringAttr(message, 'email');
255 if (!email) {
256 throw new Error('Missing required parameter: email');
259 if (this.hostState_ !== remoting.HostSession.State.UNKNOWN) {
260 throw new Error('An existing connection is in progress.');
263 this.showConfirmDialog_().then(
264 this.initializeHost_.bind(this)
265 ).then(
266 this.fetchOAuthToken_.bind(this)
267 ).then(
268 this.connectToHost_.bind(this, email),
269 this.sendErrorResponse_.bind(this, message)
274 * Prompts the user before starting the It2Me Native Messaging Host. This
275 * ensures that even if Hangouts is compromised, an attacker cannot start the
276 * host without explicit user confirmation.
278 * @return {Promise} A promise that resolves to a boolean value, indicating
279 * whether the user accepts the remote assistance or not.
280 * @private
282 remoting.It2MeHelpeeChannel.prototype.showConfirmDialog_ = function() {
283 if (base.isAppsV2()) {
284 return this.showConfirmDialogV2_();
285 } else {
286 return this.showConfirmDialogV1_();
291 * @return {Promise} A promise that resolves to a boolean value, indicating
292 * whether the user accepts the remote assistance or not.
293 * @private
295 remoting.It2MeHelpeeChannel.prototype.showConfirmDialogV1_ = function() {
296 var messageHeader = l10n.getTranslationOrError(
297 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_1');
298 var message1 = l10n.getTranslationOrError(
299 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_2');
300 var message2 = l10n.getTranslationOrError(
301 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_3');
302 var message = base.escapeHTML(messageHeader) + '\n' +
303 '- ' + base.escapeHTML(message1) + '\n' +
304 '- ' + base.escapeHTML(message2) + '\n';
306 if(window.confirm(message)) {
307 return Promise.resolve();
308 } else {
309 return Promise.reject(new Error(remoting.Error.CANCELLED));
314 * @return {Promise} A promise that resolves to a boolean value, indicating
315 * whether the user accepts the remote assistance or not.
316 * @private
318 remoting.It2MeHelpeeChannel.prototype.showConfirmDialogV2_ = function() {
319 var messageHeader = l10n.getTranslationOrError(
320 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_1');
321 var message1 = l10n.getTranslationOrError(
322 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_2');
323 var message2 = l10n.getTranslationOrError(
324 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_MESSAGE_3');
325 var message = '<div>' + base.escapeHTML(messageHeader) + '</div>' +
326 '<ul class="insetList">' +
327 '<li>' + base.escapeHTML(message1) + '</li>' +
328 '<li>' + base.escapeHTML(message2) + '</li>' +
329 '</ul>';
331 * @param {function(*=):void} resolve
332 * @param {function(*=):void} reject
334 return new Promise(function(resolve, reject) {
335 /** @param {number} result */
336 function confirmDialogCallback(result) {
337 if (result === 1) {
338 resolve();
339 } else {
340 reject(new Error(remoting.Error.CANCELLED));
343 remoting.MessageWindow.showConfirmWindow(
344 '', // Empty string to use the package name as the dialog title.
345 message,
346 l10n.getTranslationOrError(
347 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_ACCEPT'),
348 l10n.getTranslationOrError(
349 /*i18n-content*/'HANGOUTS_CONFIRM_DIALOG_DECLINE'),
350 confirmDialogCallback
356 * @return {Promise} A promise that resolves when the host is initialized.
357 * @private
359 remoting.It2MeHelpeeChannel.prototype.initializeHost_ = function() {
360 /** @type {remoting.It2MeHostFacade} */
361 var host = this.host_;
364 * @param {function(*=):void} resolve
365 * @param {function(*=):void} reject
367 return new Promise(function(resolve, reject) {
368 if (host.initialized()) {
369 resolve();
370 } else {
371 host.initialize(resolve, reject);
377 * TODO(kelvinp): The existing implementation only works in the v2 app
378 * We need to implement token fetching for the v1 app using remoting.OAuth2
379 * before launch (crbug.com/405130).
381 * @return {Promise} Promise that resolves with the OAuth token as the value.
383 remoting.It2MeHelpeeChannel.prototype.fetchOAuthToken_ = function() {
384 if (!base.isAppsV2()) {
385 throw new Error('fetchOAuthToken_ is not implemented in the v1 app.');
389 * @param {function(*=):void} resolve
391 return new Promise(function(resolve){
392 chrome.identity.getAuthToken({ 'interactive': false }, resolve);
397 * Connects to the It2Me Native Messaging Host and retrieves the access code
398 * in the |onHostStateChanged_| callback.
400 * @param {string} email
401 * @param {string} accessToken
402 * @private
404 remoting.It2MeHelpeeChannel.prototype.connectToHost_ =
405 function(email, accessToken) {
406 base.debug.assert(this.host_.initialized());
407 this.host_.connect(
408 email,
409 'oauth2:' + accessToken,
410 this.onHostStateChanged_.bind(this),
411 base.doNothing, // Ignore |onNatPolicyChanged|.
412 console.log.bind(console), // Forward logDebugInfo to console.log.
413 remoting.settings.XMPP_SERVER_ADDRESS,
414 remoting.settings.XMPP_SERVER_USE_TLS,
415 remoting.settings.DIRECTORY_BOT_JID,
416 this.onHostConnectError_);
420 * @param {remoting.Error} error
421 * @private
423 remoting.It2MeHelpeeChannel.prototype.onHostConnectError_ = function(error) {
424 this.sendErrorResponse_(null, error);
428 * @param {remoting.HostSession.State} state
429 * @private
431 remoting.It2MeHelpeeChannel.prototype.onHostStateChanged_ = function(state) {
432 this.hostState_ = state;
433 var MessageTypes = remoting.It2MeHelpeeChannel.HangoutMessageTypes;
434 var HostState = remoting.HostSession.State;
436 switch (state) {
437 case HostState.RECEIVED_ACCESS_CODE:
438 var accessCode = this.host_.getAccessCode();
439 this.hangoutPort_.postMessage({
440 method: MessageTypes.CONNECT_RESPONSE,
441 accessCode: accessCode
443 break;
444 case HostState.CONNECTED:
445 case HostState.DISCONNECTED:
446 this.hangoutPort_.postMessage({
447 method: MessageTypes.HOST_STATE_CHANGED,
448 state: state
450 break;
451 case HostState.ERROR:
452 this.sendErrorResponse_(null, remoting.Error.UNEXPECTED);
453 break;
454 case HostState.INVALID_DOMAIN_ERROR:
455 this.sendErrorResponse_(null, remoting.Error.INVALID_HOST_DOMAIN);
456 break;
457 default:
458 // It is safe to ignore other state changes.
463 * @param {?{method:string, data:Object.<string,*>}} incomingMessage
464 * @param {string|Error} error
465 * @private
467 remoting.It2MeHelpeeChannel.prototype.sendErrorResponse_ =
468 function(incomingMessage, error) {
469 if (error instanceof Error) {
470 error = error.message;
473 console.error('Error responding to message method:' +
474 (incomingMessage ? incomingMessage.method : 'null') +
475 ' error:' + error);
476 this.hangoutPort_.postMessage({
477 method: remoting.It2MeHelpeeChannel.HangoutMessageTypes.ERROR,
478 message: error,
479 request: incomingMessage