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.
7 * This class implements the functionality that is specific to application
8 * remoting ("AppRemoting" or AR).
13 /** @suppress {duplicate} */
14 var remoting
= remoting
|| {};
17 * @param {remoting.Application} app The main app that owns this delegate.
19 * @implements {remoting.Application.Delegate}
21 remoting
.AppRemoting = function(app
) {
22 app
.setDelegate(this);
25 * @type {remoting.ApplicationContextMenu}
28 this.contextMenu_
= null;
31 * @type {remoting.KeyboardLayoutsMenu}
34 this.keyboardLayoutsMenu_
= null;
37 * @type {remoting.WindowActivationMenu}
40 this.windowActivationMenu_
= null;
46 this.pingTimerId_
= 0;
50 * Type definition for the RunApplicationResponse returned by the API.
55 remoting
.AppRemoting
.AppHostResponse = function() {
61 this.authorizationCode
= '';
63 this.sharedSecret
= '';
74 * Callback for when the userinfo (email and user name) is available from
77 * @param {string} email The user's email address.
78 * @param {string} fullName The user's full name.
79 * @return {void} Nothing.
81 remoting
.onUserInfoAvailable = function(email
, fullName
) {
85 * Initialize the application and register all event handlers. After this
86 * is called, the app is running and waiting for user events.
88 * @param {remoting.SessionConnector} connector
89 * @return {void} Nothing.
91 remoting
.AppRemoting
.prototype.init = function(connector
) {
92 remoting
.initGlobalObjects();
93 remoting
.initIdentity(remoting
.onUserInfoAvailable
);
95 // TODO(jamiewalch): Remove ClientSession's dependency on remoting.fullscreen
96 // so that this is no longer required.
97 remoting
.fullscreen
= new remoting
.FullscreenAppsV2();
99 var restoreHostWindows = function() {
100 if (remoting
.clientSession
) {
101 remoting
.clientSession
.sendClientMessage('restoreAllWindows', '');
104 chrome
.app
.window
.current().onRestored
.addListener(restoreHostWindows
);
106 remoting
.windowShape
.updateClientWindowShape();
108 // Initialize the context menus.
109 if (remoting
.platformIsChromeOS()) {
110 var adapter
= new remoting
.ContextMenuChrome();
112 var adapter
= new remoting
.ContextMenuDom(
113 document
.getElementById('context-menu'));
115 this.contextMenu_
= new remoting
.ApplicationContextMenu(adapter
);
116 this.keyboardLayoutsMenu_
= new remoting
.KeyboardLayoutsMenu(adapter
);
117 this.windowActivationMenu_
= new remoting
.WindowActivationMenu(adapter
);
119 /** @type {remoting.AppRemoting} */
122 /** @param {XMLHttpRequest} xhr */
123 var parseAppHostResponse = function(xhr
) {
124 if (xhr
.status
== 200) {
125 var response
= /** @type {remoting.AppRemoting.AppHostResponse} */
126 (base
.jsonParseSafe(xhr
.responseText
));
129 response
.status
== 'done' &&
131 response
.authorizationCode
&&
132 response
.sharedSecret
&&
134 response
.host
.hostId
) {
135 var hostJid
= response
.hostJid
;
136 that
.contextMenu_
.setHostId(response
.host
.hostId
);
137 var host
= new remoting
.Host
;
138 host
.hostId
= response
.host
.hostId
;
139 host
.jabberId
= hostJid
;
140 host
.authorizationCode
= response
.authorizationCode
;
141 host
.sharedSecret
= response
.sharedSecret
;
143 remoting
.setMode(remoting
.AppMode
.CLIENT_CONNECTING
);
145 var idleDetector
= new remoting
.IdleDetector(
146 document
.getElementById('idle-dialog'),
147 remoting
.disconnect
);
150 * @param {string} tokenUrl Token-issue URL received from the host.
151 * @param {string} hostPublicKey Host public key (DER and Base64
153 * @param {string} scope OAuth scope to request the token for.
154 * @param {function(string, string):void} onThirdPartyTokenFetched
157 var fetchThirdPartyToken = function(
158 tokenUrl
, hostPublicKey
, scope
, onThirdPartyTokenFetched
) {
159 // Use the authentication tokens returned by the app-remoting server.
160 onThirdPartyTokenFetched(host
['authorizationCode'],
161 host
['sharedSecret']);
164 connector
.connectMe2App(host
, fetchThirdPartyToken
);
165 } else if (response
&& response
.status
== 'pending') {
166 that
.handleError(remoting
.Error
.SERVICE_UNAVAILABLE
);
169 console
.error('Invalid "runApplication" response from server.');
170 // TODO(garykac) Start using remoting.Error.fromHttpStatus once it has
171 // been updated to properly report 'unknown' errors (rather than
172 // reporting them as AUTHENTICATION_FAILED).
173 if (xhr
.status
== 0) {
174 that
.handleError(remoting
.Error
.NETWORK_FAILURE
);
175 } else if (xhr
.status
== 401) {
176 that
.handleError(remoting
.Error
.AUTHENTICATION_FAILED
);
177 } else if (xhr
.status
== 403) {
178 that
.handleError(remoting
.Error
.APP_NOT_AUTHORIZED
);
179 } else if (xhr
.status
== 502 || xhr
.status
== 503) {
180 that
.handleError(remoting
.Error
.SERVICE_UNAVAILABLE
);
182 that
.handleError(remoting
.Error
.UNEXPECTED
);
187 /** @param {string} token */
188 var getAppHost = function(token
) {
189 var headers
= { 'Authorization': 'OAuth ' + token
};
191 that
.runApplicationUrl(), parseAppHostResponse
, '', headers
);
194 /** @param {remoting.Error} error */
195 var onError = function(error
) {
196 that
.handleError(error
);
199 remoting
.LoadingWindow
.show();
201 remoting
.identity
.callWithToken(getAppHost
, onError
);
205 * @return {string} Application product name to be used in UI.
207 remoting
.AppRemoting
.prototype.getApplicationName = function() {
208 var manifest
= chrome
.runtime
.getManifest();
209 return manifest
.name
;
212 /** @return {string} */
213 remoting
.AppRemoting
.prototype.runApplicationUrl = function() {
214 return remoting
.settings
.APP_REMOTING_API_BASE_URL
+ '/applications/' +
215 remoting
.settings
.getAppRemotingApplicationId() + '/run';
219 * @return {string} The default remap keys for the current platform.
221 remoting
.AppRemoting
.prototype.getDefaultRemapKeys = function() {
222 // Map Cmd to Ctrl on Mac since hosts typically use Ctrl for keyboard
223 // shortcuts, but we want them to act as natively as possible.
224 if (remoting
.platformIsMac()) {
225 return '0x0700e3>0x0700e0,0x0700e7>0x0700e4';
231 * Called when a new session has been connected.
233 * @param {remoting.ClientSession} clientSession
234 * @return {void} Nothing.
236 remoting
.AppRemoting
.prototype.handleConnected = function(clientSession
) {
237 remoting
.clientSession
.sendClientMessage(
238 'setUserDisplayInfo',
239 JSON
.stringify({fullName
: remoting
.identity
.getCachedUserFullName()}));
241 // Set up a ping at 10-second intervals to test the connection speed.
243 var message
= { timestamp
: new Date().getTime() };
244 clientSession
.sendClientMessage('pingRequest', JSON
.stringify(message
));
247 this.pingTimerId_
= window
.setInterval(ping
, 10 * 1000);
251 * Called when the current session has been disconnected.
253 * @return {void} Nothing.
255 remoting
.AppRemoting
.prototype.handleDisconnected = function() {
256 // Cancel the ping when the connection closes.
257 window
.clearInterval(this.pingTimerId_
);
259 chrome
.app
.window
.current().close();
263 * Called when the current session's connection has failed.
265 * @param {remoting.SessionConnector} connector
266 * @param {remoting.Error} error
267 * @return {void} Nothing.
269 remoting
.AppRemoting
.prototype.handleConnectionFailed = function(
271 this.handleError(error
);
275 * Called when the current session has reached the point where the host has
276 * started streaming video frames to the client.
278 * @return {void} Nothing.
280 remoting
.AppRemoting
.prototype.handleVideoStreamingStarted = function() {
281 remoting
.LoadingWindow
.close();
285 * Called when an extension message needs to be handled.
287 * @param {string} type The type of the extension message.
288 * @param {Object} message The parsed extension message data.
289 * @return {boolean} True if the extension message was recognized.
291 remoting
.AppRemoting
.prototype.handleExtensionMessage = function(
296 // URL requests from the hosted app are untrusted, so disallow anything
297 // other than HTTP or HTTPS.
298 var url
= getStringAttr(message
, 'url');
299 if (url
.indexOf('http:') != 0 && url
.indexOf('https:') != 0) {
300 console
.error('Bad URL: ' + url
);
306 case 'onWindowRemoved':
307 var id
= getNumberAttr(message
, 'id');
308 this.windowActivationMenu_
.remove(id
);
311 case 'onWindowAdded':
312 var id
= getNumberAttr(message
, 'id');
313 var title
= getStringAttr(message
, 'title');
314 this.windowActivationMenu_
.add(id
, title
);
317 case 'onAllWindowsMinimized':
318 chrome
.app
.window
.current().minimize();
321 case 'setKeyboardLayouts':
322 var supportedLayouts
= getArrayAttr(message
, 'supportedLayouts');
323 var currentLayout
= getStringAttr(message
, 'currentLayout');
324 console
.log('Current host keyboard layout: ' + currentLayout
);
325 console
.log('Supported host keyboard layouts: ' + supportedLayouts
);
326 this.keyboardLayoutsMenu_
.setLayouts(supportedLayouts
, currentLayout
);
330 var then
= getNumberAttr(message
, 'timestamp');
331 var now
= new Date().getTime();
332 this.contextMenu_
.updateConnectionRTT(now
- then
);
340 * Called when an error needs to be displayed to the user.
342 * @param {remoting.Error} errorTag The error to be localized and displayed.
343 * @return {void} Nothing.
345 remoting
.AppRemoting
.prototype.handleError = function(errorTag
) {
346 console
.error('Connection failed: ' + errorTag
);
347 remoting
.LoadingWindow
.close();
348 remoting
.MessageWindow
.showErrorMessage(
349 chrome
.i18n
.getMessage(/*i18n-content*/'CONNECTION_FAILED'),
350 chrome
.i18n
.getMessage(/** @type {string} */ (errorTag
)));