[go: up one dir, main page]

blob: 993f077dbc495811bc09f6b7ef084eee292d886b [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* This class implements the functionality that is specific to application
* remoting ("AppRemoting" or AR).
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @param {remoting.Application} app The main app that owns this delegate.
* @constructor
* @implements {remoting.Application.Delegate}
*/
remoting.AppRemoting = function(app) {
app.setDelegate(this);
/**
* @type {remoting.ApplicationContextMenu}
* @private
*/
this.contextMenu_ = null;
/**
* @type {remoting.KeyboardLayoutsMenu}
* @private
*/
this.keyboardLayoutsMenu_ = null;
/**
* @type {remoting.WindowActivationMenu}
* @private
*/
this.windowActivationMenu_ = null;
};
/**
* Type definition for the RunApplicationResponse returned by the API.
*
* @constructor
* @private
*/
remoting.AppRemoting.AppHostResponse = function() {
/** @type {string} */
this.status = '';
/** @type {string} */
this.hostJid = '';
/** @type {string} */
this.authorizationCode = '';
/** @type {string} */
this.sharedSecret = '';
this.host = {
/** @type {string} */
applicationId: '',
/** @type {string} */
hostId: ''};
};
/**
* Callback for when the userinfo (email and user name) is available from
* the identity API.
*
* @param {string} email The user's email address.
* @param {string} fullName The user's full name.
* @return {void} Nothing.
*/
remoting.onUserInfoAvailable = function(email, fullName) {
};
/**
* Initialize the application and register all event handlers. After this
* is called, the app is running and waiting for user events.
*
* @param {remoting.SessionConnector} connector
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.init = function(connector) {
remoting.initGlobalObjects();
remoting.initIdentity(remoting.onUserInfoAvailable);
remoting.initGlobalEventHandlers();
var restoreHostWindows = function() {
if (remoting.clientSession) {
remoting.clientSession.sendClientMessage('restoreAllWindows', '');
}
};
chrome.app.window.current().onRestored.addListener(restoreHostWindows);
remoting.windowShape.updateClientWindowShape();
// Initialize the context menus.
if (remoting.platformIsChromeOS()) {
var adapter = new remoting.ContextMenuChrome();
} else {
var adapter = new remoting.ContextMenuDom(
document.getElementById('context-menu'));
}
this.contextMenu_ = new remoting.ApplicationContextMenu(adapter);
this.keyboardLayoutsMenu_ = new remoting.KeyboardLayoutsMenu(adapter);
this.windowActivationMenu_ = new remoting.WindowActivationMenu(adapter);
/** @type {remoting.AppRemoting} */
var that = this;
/** @param {XMLHttpRequest} xhr */
var parseAppHostResponse = function(xhr) {
if (xhr.status == 200) {
var response = /** @type {remoting.AppRemoting.AppHostResponse} */
(base.jsonParseSafe(xhr.responseText));
if (response &&
response.status &&
response.status == 'done' &&
response.hostJid &&
response.authorizationCode &&
response.sharedSecret &&
response.host &&
response.host.hostId) {
var hostJid = response.hostJid;
that.contextMenu_.setHostId(response.host.hostId);
var host = new remoting.Host;
host.hostId = response.host.hostId;
host.jabberId = hostJid;
host.authorizationCode = response.authorizationCode;
host.sharedSecret = response.sharedSecret;
remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
var idleDetector = new remoting.IdleDetector(
document.getElementById('idle-dialog'),
remoting.disconnect);
/**
* @param {string} tokenUrl Token-issue URL received from the host.
* @param {string} hostPublicKey Host public key (DER and Base64
* encoded).
* @param {string} scope OAuth scope to request the token for.
* @param {function(string, string):void} onThirdPartyTokenFetched
* Callback.
*/
var fetchThirdPartyToken = function(
tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
// Use the authentication tokens returned by the app-remoting server.
onThirdPartyTokenFetched(host['authorizationCode'],
host['sharedSecret']);
};
connector.connectMe2App(host, fetchThirdPartyToken);
} else if (response && response.status == 'pending') {
that.handleError(remoting.Error.SERVICE_UNAVAILABLE);
}
} else {
console.error('Invalid "runApplication" response from server.');
// TODO(garykac) Start using remoting.Error.fromHttpStatus once it has
// been updated to properly report 'unknown' errors (rather than
// reporting them as AUTHENTICATION_FAILED).
if (xhr.status == 0) {
that.handleError(remoting.Error.NETWORK_FAILURE);
} else if (xhr.status == 401) {
that.handleError(remoting.Error.AUTHENTICATION_FAILED);
} else if (xhr.status == 403) {
that.handleError(remoting.Error.NOT_AUTHORIZED);
} else if (xhr.status == 502 || xhr.status == 503) {
that.handleError(remoting.Error.SERVICE_UNAVAILABLE);
} else {
that.handleError(remoting.Error.UNEXPECTED);
}
}
};
/** @param {string} token */
var getAppHost = function(token) {
var headers = { 'Authorization': 'OAuth ' + token };
remoting.xhr.post(
that.runApplicationUrl(), parseAppHostResponse, '', headers);
};
/** @param {remoting.Error} error */
var onError = function(error) {
that.handleError(error);
};
remoting.LoadingWindow.show();
remoting.identity.callWithToken(getAppHost, onError);
}
/**
* @return {string} The default remap keys for the current platform.
*/
remoting.AppRemoting.prototype.getDefaultRemapKeys = function() {
// Map Cmd to Ctrl on Mac since hosts typically use Ctrl for keyboard
// shortcuts, but we want them to act as natively as possible.
if (remoting.platformIsMac()) {
return '0x0700e3>0x0700e0,0x0700e7>0x0700e4';
}
return '';
};
/**
* @return {Array.<string>} A list of |ClientSession.Capability|s required
* by this application.
*/
remoting.AppRemoting.prototype.getRequiredCapabilities = function() {
return [
remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION,
remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS,
remoting.ClientSession.Capability.VIDEO_RECORDER,
remoting.ClientSession.Capability.GOOGLE_DRIVE
];
};
/**
* Called when a new session has been connected.
*
* @param {remoting.ClientSession} clientSession
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleConnected = function(clientSession) {
remoting.clientSession.sendClientMessage(
'setUserDisplayInfo',
JSON.stringify({fullName: remoting.identity.getCachedUserFullName()}));
// Set up a ping at 10-second intervals to test the connection speed.
function ping() {
var message = { timestamp: new Date().getTime() };
clientSession.sendClientMessage('pingRequest', JSON.stringify(message));
};
ping();
var timerId = window.setInterval(ping, 10 * 1000);
// Cancel the ping when the connection closes.
clientSession.addEventListener(
remoting.ClientSession.Events.stateChanged,
/** @param {remoting.ClientSession.StateEvent} state */
function(state) {
if (state.current === remoting.ClientSession.State.CLOSED ||
state.current === remoting.ClientSession.State.FAILED) {
window.clearInterval(timerId);
}
});
};
/**
* Called when the current session has been disconnected.
*
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleDisconnected = function() {
chrome.app.window.current().close();
};
/**
* Called when the current session's connection has failed.
*
* @param {remoting.SessionConnector} connector
* @param {remoting.Error} error
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleConnectionFailed = function(
connector, error) {
this.handleError(error);
};
/**
* Called when the current session has reached the point where the host has
* started streaming video frames to the client.
*
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleVideoStreamingStarted = function() {
remoting.LoadingWindow.close();
};
/**
* Called when an extension message needs to be handled.
*
* @param {string} type The type of the extension message.
* @param {Object} message The parsed extension message data.
* @return {boolean} True if the extension message was recognized.
*/
remoting.AppRemoting.prototype.handleExtensionMessage = function(
type, message) {
switch (type) {
case 'openURL':
// URL requests from the hosted app are untrusted, so disallow anything
// other than HTTP or HTTPS.
var url = getStringAttr(message, 'url');
if (url.indexOf('http:') != 0 && url.indexOf('https:') != 0) {
console.error('Bad URL: ' + url);
} else {
window.open(url);
}
return true;
case 'onWindowRemoved':
var id = getNumberAttr(message, 'id');
this.windowActivationMenu_.remove(id);
return true;
case 'onWindowAdded':
var id = getNumberAttr(message, 'id');
var title = getStringAttr(message, 'title');
this.windowActivationMenu_.add(id, title);
return true;
case 'onAllWindowsMinimized':
chrome.app.window.current().minimize();
return true;
case 'setKeyboardLayouts':
var supportedLayouts = getArrayAttr(message, 'supportedLayouts');
var currentLayout = getStringAttr(message, 'currentLayout');
console.log('Current host keyboard layout: ' + currentLayout);
console.log('Supported host keyboard layouts: ' + supportedLayouts);
this.keyboardLayoutsMenu_.setLayouts(supportedLayouts, currentLayout);
return true;
case 'pingResponse':
var then = getNumberAttr(message, 'timestamp');
var now = new Date().getTime();
this.contextMenu_.updateConnectionRTT(now - then);
return true;
}
return false;
};
/**
* Called when an error needs to be displayed to the user.
*
* @param {remoting.Error} errorTag The error to be localized and displayed.
* @return {void} Nothing.
*/
remoting.AppRemoting.prototype.handleError = function(errorTag) {
console.error('Connection failed: ' + errorTag);
remoting.LoadingWindow.close();
remoting.MessageWindow.showErrorMessage(
chrome.i18n.getMessage(/**i18n-content*/'CONNECTION_FAILED'),
chrome.i18n.getMessage(/** @type {string} */ (errorTag)));
};
/** @return {string} */
remoting.AppRemoting.prototype.runApplicationUrl = function() {
return remoting.settings.APP_REMOTING_API_BASE_URL + '/applications/' +
remoting.settings.getAppRemotingApplicationId() + '/run';
};