Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: remoting/webapp/app_remoting/js/app_remoting.js

Issue 1082973002: [Webapp Refactor] Implement AppRemotingActivity. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@ClientEventHandler
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * This class implements the functionality that is specific to application 7 * This class implements the functionality that is specific to application
8 * remoting ("AppRemoting" or AR). 8 * remoting ("AppRemoting" or AR).
9 */ 9 */
10 10
11 'use strict'; 11 'use strict';
12 12
13 /** @suppress {duplicate} */ 13 /** @suppress {duplicate} */
14 var remoting = remoting || {}; 14 var remoting = remoting || {};
15 15
16 /** 16 /**
17 * Interval to test the connection speed.
18 * @const {number}
19 */
20 var CONNECTION_SPEED_PING_INTERVAL_MS = 10 * 1000;
21
22 /**
23 * Interval to refresh the google drive access token.
24 * @const {number}
25 */
26 var DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS = 15 * 60 * 1000;
27
28 /**
29 * @param {Array<string>} appCapabilities Array of application capabilities.
30 * @constructor 17 * @constructor
31 * @implements {remoting.ApplicationInterface} 18 * @implements {remoting.ApplicationInterface}
32 * @implements {remoting.ProtocolExtension}
33 * @implements {remoting.ClientSession.EventHandler}
34 * @extends {remoting.Application} 19 * @extends {remoting.Application}
35 */ 20 */
36 remoting.AppRemoting = function(appCapabilities) { 21 remoting.AppRemoting = function() {
37 base.inherits(this, remoting.Application); 22 base.inherits(this, remoting.Application);
38 23
39 /** @private {remoting.ApplicationContextMenu} */ 24 /** @private {remoting.Activity} */
40 this.contextMenu_ = null; 25 this.activity_ = null;
41
42 /** @private {remoting.KeyboardLayoutsMenu} */
43 this.keyboardLayoutsMenu_ = null;
44
45 /** @private {remoting.WindowActivationMenu} */
46 this.windowActivationMenu_ = null;
47
48 /** @private {remoting.AppConnectedView} */
49 this.connectedView_ = null;
50
51 /** @private {base.Disposables} */
52 this.extensionDisposables_ = null;
53
54 /** @private */
55 this.supportsGoogleDrive_ = false;
56
57 /** @private */
58 this.sessionConnector_ = remoting.SessionConnector.factory.createConnector(
59 document.getElementById('client-container'),
60 appCapabilities, this);
61 }; 26 };
62 27
63 /** 28 /**
64 * Type definition for the RunApplicationResponse returned by the API.
65 *
66 * @constructor
67 * @private
68 */
69 remoting.AppRemoting.AppHostResponse = function() {
70 /** @type {string} */
71 this.status = '';
72 /** @type {string} */
73 this.hostJid = '';
74 /** @type {string} */
75 this.authorizationCode = '';
76 /** @type {string} */
77 this.sharedSecret = '';
78
79 this.host = {
80 /** @type {string} */
81 applicationId: '',
82
83 /** @type {string} */
84 hostId: ''};
85 };
86
87 /**
88 * @return {string} Application product name to be used in UI. 29 * @return {string} Application product name to be used in UI.
89 * @override {remoting.ApplicationInterface} 30 * @override {remoting.ApplicationInterface}
90 */ 31 */
91 remoting.AppRemoting.prototype.getApplicationName = function() { 32 remoting.AppRemoting.prototype.getApplicationName = function() {
92 var manifest = chrome.runtime.getManifest(); 33 var manifest = chrome.runtime.getManifest();
93 return manifest.name; 34 return manifest.name;
94 }; 35 };
95 36
96 /** 37 /**
97 * @param {!remoting.Error} error The failure reason. 38 * @param {!remoting.Error} error The failure reason.
98 * @override {remoting.ApplicationInterface} 39 * @override {remoting.ApplicationInterface}
99 */ 40 */
100 remoting.AppRemoting.prototype.signInFailed_ = function(error) { 41 remoting.AppRemoting.prototype.signInFailed_ = function(error) {
101 this.onError(error); 42 remoting.MessageWindow.showErrorMessage(
43 chrome.i18n.getMessage(/*i18n-content*/'CONNECTION_FAILED'),
44 chrome.i18n.getMessage(error.getTag()));
102 }; 45 };
103 46
104 /** 47 /**
105 * @override {remoting.ApplicationInterface} 48 * @override {remoting.ApplicationInterface}
106 */ 49 */
107 remoting.AppRemoting.prototype.initApplication_ = function() { 50 remoting.AppRemoting.prototype.initApplication_ = function() {
108 // TODO(jamiewalch): Remove ClientSession's dependency on remoting.fullscreen
kelvinp 2015/04/14 00:46:33 No longer needed, as we did such a good job cleanu
garykac 2015/04/15 00:52:37 Wahoo!
109 // so that this is no longer required.
110 remoting.fullscreen = new remoting.FullscreenAppsV2();
111
112 remoting.windowShape.updateClientWindowShape(); 51 remoting.windowShape.updateClientWindowShape();
113 52
114 // Initialize the context menus. 53 this.activity_ = new remoting.AppRemotingActivity();
115 if (remoting.platformIsChromeOS()) {
116 var adapter = new remoting.ContextMenuChrome();
117 } else {
118 var adapter = new remoting.ContextMenuDom(
119 document.getElementById('context-menu'));
120 }
121 this.contextMenu_ = new remoting.ApplicationContextMenu(adapter);
122 this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(adapter);
123 this.windowActivationMenu_ = new remoting.WindowActivationMenu(adapter);
124 }; 54 };
125 55
126 /** 56 /**
127 * @param {string} token An OAuth access token. 57 * @param {string} token An OAuth access token.
128 * @override {remoting.ApplicationInterface} 58 * @override {remoting.ApplicationInterface}
129 */ 59 */
130 remoting.AppRemoting.prototype.startApplication_ = function(token) { 60 remoting.AppRemoting.prototype.startApplication_ = function(token) {
kelvinp 2015/04/14 00:46:33 Moved to AppRemotingActivity
131 remoting.LoadingWindow.show(); 61 this.activity_.start();
132
133 /** @type {remoting.AppRemoting} */
134 var that = this;
135
136 /** @param {!remoting.Xhr.Response} xhrResponse */
137 var parseAppHostResponse = function(xhrResponse) {
138 if (xhrResponse.status == 200) {
139 var response = /** @type {remoting.AppRemoting.AppHostResponse} */
140 (base.jsonParseSafe(xhrResponse.getText()));
141 if (response &&
142 response.status &&
143 response.status == 'done' &&
144 response.hostJid &&
145 response.authorizationCode &&
146 response.sharedSecret &&
147 response.host &&
148 response.host.hostId) {
149 var hostJid = response.hostJid;
150 that.contextMenu_.setHostId(response.host.hostId);
151 var host = new remoting.Host(response.host.hostId);
152 host.jabberId = hostJid;
153 host.authorizationCode = response.authorizationCode;
154 host.sharedSecret = response.sharedSecret;
155
156 remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
157
158 var idleDetector = new remoting.IdleDetector(
159 document.getElementById('idle-dialog'),
160 remoting.app.disconnect.bind(remoting.app));
161
162 /**
163 * @param {string} tokenUrl Token-issue URL received from the host.
164 * @param {string} hostPublicKey Host public key (DER and Base64
165 * encoded).
166 * @param {string} scope OAuth scope to request the token for.
167 * @param {function(string, string):void} onThirdPartyTokenFetched
168 * Callback.
169 */
170 var fetchThirdPartyToken = function(
171 tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
172 // Use the authentication tokens returned by the app-remoting server.
173 onThirdPartyTokenFetched(host['authorizationCode'],
174 host['sharedSecret']);
175 };
176
177 that.sessionConnector_.connect(
178 remoting.Application.Mode.APP_REMOTING, host,
179 new remoting.CredentialsProvider(
180 {fetchThirdPartyToken: fetchThirdPartyToken}));
181 } else if (response && response.status == 'pending') {
182 that.onError(new remoting.Error(
183 remoting.Error.Tag.SERVICE_UNAVAILABLE));
184 }
185 } else {
186 console.error('Invalid "runApplication" response from server.');
187 that.onError(remoting.Error.fromHttpStatus(xhrResponse.status));
188 }
189 };
190
191 new remoting.Xhr({
192 method: 'POST',
193 url: that.runApplicationUrl_(),
194 oauthToken: token
195 }).start().then(parseAppHostResponse);
196 }; 62 };
197 63
198 /** 64 /**
199 * @override {remoting.ApplicationInterface} 65 * @override {remoting.ApplicationInterface}
200 */ 66 */
201 remoting.AppRemoting.prototype.exitApplication_ = function() { 67 remoting.AppRemoting.prototype.exitApplication_ = function() {
202 remoting.LoadingWindow.close(); 68 this.activity_.dispose();
203 this.closeMainWindow_(); 69 this.closeMainWindow_();
204 }; 70 };
205
206 /**
207 * @param {remoting.ConnectionInfo} connectionInfo
kelvinp 2015/04/14 00:46:33 Moved to AppRemotingActivity
208 */
209 remoting.AppRemoting.prototype.onConnected = function(connectionInfo) {
210 this.supportsGoogleDrive_ = connectionInfo.session().hasCapability(
211 remoting.ClientSession.Capability.GOOGLE_DRIVE);
212
213 connectionInfo.plugin().extensions().register(this);
214
215 this.connectedView_ = new remoting.AppConnectedView(
216 document.getElementById('client-container'), connectionInfo);
217
218 // Map Cmd to Ctrl on Mac since hosts typically use Ctrl for keyboard
219 // shortcuts, but we want them to act as natively as possible.
220 if (remoting.platformIsMac()) {
221 connectionInfo.plugin().setRemapKeys('0x0700e3>0x0700e0,0x0700e7>0x0700e4');
222 }
223 };
224
225 remoting.AppRemoting.prototype.onDisconnected = function() {
226 base.dispose(this.connectedView_);
227 this.connectedView_ = null;
228 this.stopExtension_();
229 chrome.app.window.current().close();
230 };
231
232 /**
233 * @param {!remoting.Error} error
234 */
235 remoting.AppRemoting.prototype.onConnectionFailed = function(error) {
236 this.onError(error);
237 };
238
239 /**
240 * @param {!remoting.Error} error The error to be localized and displayed.
241 */
242 remoting.AppRemoting.prototype.onError = function(error) {
243 console.error('Connection failed: ' + error.toString());
244 remoting.LoadingWindow.close();
245 remoting.MessageWindow.showErrorMessage(
246 chrome.i18n.getMessage(/*i18n-content*/'CONNECTION_FAILED'),
247 chrome.i18n.getMessage(error.getTag()));
248 this.stopExtension_();
249 };
250
251
252 /**
kelvinp 2015/04/14 00:46:33 Moved to AppConnectedView
253 * @return {Array<string>}
254 * @override {remoting.ProtocolExtension}
255 */
256 remoting.AppRemoting.prototype.getExtensionTypes = function() {
257 return ['openURL', 'onWindowRemoved', 'onWindowAdded',
258 'onAllWindowsMinimized', 'setKeyboardLayouts', 'pingResponse'];
259 };
260
261 /**
262 * @param {function(string,string)} sendMessageToHost Callback to send a message
263 * to the host.
264 * @override {remoting.ProtocolExtension}
265 */
266 remoting.AppRemoting.prototype.startExtension = function(sendMessageToHost) {
267 this.windowActivationMenu_.setExtensionMessageSender(sendMessageToHost);
268 this.keyboardLayoutsMenu_.setExtensionMessageSender(sendMessageToHost);
269
270 remoting.identity.getUserInfo().then(function(userInfo) {
271 sendMessageToHost('setUserDisplayInfo',
272 JSON.stringify({fullName: userInfo.name}));
273 });
274
275 base.dispose(this.extensionDisposables_);
276
277 var onRestoreHook = new base.ChromeEventHook(
278 chrome.app.window.current().onRestored, function() {
279 sendMessageToHost('restoreAllWindows', '');
280 });
281
282 var pingTimer = new base.RepeatingTimer(function() {
283 var message = {timestamp: new Date().getTime()};
284 sendMessageToHost('pingRequest', JSON.stringify(message));
285 }, CONNECTION_SPEED_PING_INTERVAL_MS);
286
287 this.extensionDisposables_ = new base.Disposables(onRestoreHook, pingTimer);
288
289 if (this.supportsGoogleDrive_) {
290 this.extensionDisposables_.add(new base.RepeatingTimer(
291 this.sendGoogleDriveAccessToken_.bind(this, sendMessageToHost),
292 DRIVE_ACCESS_TOKEN_REFRESH_INTERVAL_MS, true));
293 }
294 };
295
296 /** @private */
297 remoting.AppRemoting.prototype.stopExtension_ = function() {
298 this.windowActivationMenu_.setExtensionMessageSender(base.doNothing);
299 this.keyboardLayoutsMenu_.setExtensionMessageSender(base.doNothing);
300
301 base.dispose(this.extensionDisposables_);
302 this.extensionDisposables_ = null;
303 };
304
305 /**
306 * @param {string} type The message type.
307 * @param {Object} message The parsed extension message data.
308 * @override {remoting.ProtocolExtension}
309 */
310 remoting.AppRemoting.prototype.onExtensionMessage = function(type, message) {
311 switch (type) {
312
313 case 'openURL':
314 // URL requests from the hosted app are untrusted, so disallow anything
315 // other than HTTP or HTTPS.
316 var url = base.getStringAttr(message, 'url');
317 if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
318 console.error('Bad URL: ' + url);
319 } else {
320 window.open(url);
321 }
322 return true;
323
324 case 'onWindowRemoved':
325 var id = base.getNumberAttr(message, 'id');
326 this.windowActivationMenu_.remove(id);
327 return true;
328
329 case 'onWindowAdded':
330 var id = base.getNumberAttr(message, 'id');
331 var title = base.getStringAttr(message, 'title');
332 this.windowActivationMenu_.add(id, title);
333 return true;
334
335 case 'onAllWindowsMinimized':
336 chrome.app.window.current().minimize();
337 return true;
338
339 case 'setKeyboardLayouts':
340 var supportedLayouts = base.getArrayAttr(message, 'supportedLayouts');
341 var currentLayout = base.getStringAttr(message, 'currentLayout');
342 console.log('Current host keyboard layout: ' + currentLayout);
343 console.log('Supported host keyboard layouts: ' + supportedLayouts);
344 this.keyboardLayoutsMenu_.setLayouts(supportedLayouts, currentLayout);
345 return true;
346
347 case 'pingResponse':
348 var then = base.getNumberAttr(message, 'timestamp');
349 var now = new Date().getTime();
350 this.contextMenu_.updateConnectionRTT(now - then);
351 return true;
352 }
353
354 return false;
355 };
356
357 /**
358 * Timer callback to send the access token to the host.
359 * @param {function(string, string)} sendExtensionMessage
360 * @private
361 */
362 remoting.AppRemoting.prototype.sendGoogleDriveAccessToken_
363 = function(sendExtensionMessage) {
364 var googleDriveScopes = [
365 'https://docs.google.com/feeds/',
366 'https://www.googleapis.com/auth/drive'
367 ];
368 remoting.identity.getNewToken(googleDriveScopes).then(
369 function(/** string */ token){
370 sendExtensionMessage('accessToken', token);
371 }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
372 console.log('Failed to refresh access token: ' + error.toString());
373 }));
374 };
375
376 /**
377 * @return {string}
378 * @private
379 */
380 remoting.AppRemoting.prototype.runApplicationUrl_ = function() {
381 return remoting.settings.APP_REMOTING_API_BASE_URL + '/applications/' +
382 remoting.settings.getAppRemotingApplicationId() + '/run';
383 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698