Rewrite AndroidSyncSettings to be significantly simpler.
[chromium-blink-merge.git] / remoting / webapp / app_remoting / js / app_remoting.js
blob636b4200c03288d5cacfa07152b281b87004baad
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
7 * This class implements the functionality that is specific to application
8 * remoting ("AppRemoting" or AR).
9 */
11 'use strict';
13 /** @suppress {duplicate} */
14 var remoting = remoting || {};
16 /**
17 * @param {remoting.Application} app The main app that owns this delegate.
18 * @constructor
19 * @implements {remoting.Application.Delegate}
21 remoting.AppRemoting = function(app) {
22 app.setDelegate(this);
24 /**
25 * @type {remoting.ApplicationContextMenu}
26 * @private
28 this.contextMenu_ = null;
30 /**
31 * @type {remoting.KeyboardLayoutsMenu}
32 * @private
34 this.keyboardLayoutsMenu_ = null;
36 /**
37 * @type {remoting.WindowActivationMenu}
38 * @private
40 this.windowActivationMenu_ = null;
42 /**
43 * @type {number}
44 * @private
46 this.pingTimerId_ = 0;
49 /**
50 * Type definition for the RunApplicationResponse returned by the API.
52 * @constructor
53 * @private
55 remoting.AppRemoting.AppHostResponse = function() {
56 /** @type {string} */
57 this.status = '';
58 /** @type {string} */
59 this.hostJid = '';
60 /** @type {string} */
61 this.authorizationCode = '';
62 /** @type {string} */
63 this.sharedSecret = '';
65 this.host = {
66 /** @type {string} */
67 applicationId: '',
69 /** @type {string} */
70 hostId: ''};
73 /**
74 * Callback for when the userinfo (email and user name) is available from
75 * the identity API.
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) {
84 /**
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();
111 } else {
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} */
120 var that = this;
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));
127 if (response &&
128 response.status &&
129 response.status == 'done' &&
130 response.hostJid &&
131 response.authorizationCode &&
132 response.sharedSecret &&
133 response.host &&
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
152 * encoded).
153 * @param {string} scope OAuth scope to request the token for.
154 * @param {function(string, string):void} onThirdPartyTokenFetched
155 * Callback.
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);
168 } else {
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);
181 } else {
182 that.handleError(remoting.Error.UNEXPECTED);
187 /** @param {string} token */
188 var getAppHost = function(token) {
189 var headers = { 'Authorization': 'OAuth ' + token };
190 remoting.xhr.post(
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';
227 return '';
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.
242 function ping() {
243 var message = { timestamp: new Date().getTime() };
244 clientSession.sendClientMessage('pingRequest', JSON.stringify(message));
246 ping();
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(
270 connector, error) {
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(
292 type, message) {
293 switch (type) {
295 case 'openURL':
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);
301 } else {
302 window.open(url);
304 return true;
306 case 'onWindowRemoved':
307 var id = getNumberAttr(message, 'id');
308 this.windowActivationMenu_.remove(id);
309 return true;
311 case 'onWindowAdded':
312 var id = getNumberAttr(message, 'id');
313 var title = getStringAttr(message, 'title');
314 this.windowActivationMenu_.add(id, title);
315 return true;
317 case 'onAllWindowsMinimized':
318 chrome.app.window.current().minimize();
319 return true;
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);
327 return true;
329 case 'pingResponse':
330 var then = getNumberAttr(message, 'timestamp');
331 var now = new Date().getTime();
332 this.contextMenu_.updateConnectionRTT(now - then);
333 return true;
336 return false;
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)));