| // Copyright 2015 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. |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| (function() { |
| |
| 'use strict'; |
| |
| /** |
| * @param {remoting.Host} host |
| * @param {remoting.HostList} hostList |
| * |
| * @constructor |
| * @implements {remoting.Activity} |
| */ |
| remoting.Me2MeActivity = function(host, hostList) { |
| /** @private */ |
| this.host_ = host; |
| /** @private */ |
| this.hostList_ = hostList; |
| /** @private */ |
| this.pinDialog_ = |
| new remoting.PinDialog(base.getHtmlElement('pin-dialog'), host); |
| /** @private */ |
| this.hostUpdateDialog_ = new remoting.HostNeedsUpdateDialog( |
| base.getHtmlElement('host-needs-update-dialog'), this.host_); |
| |
| /** @private */ |
| this.retryOnHostOffline_ = true; |
| |
| /** @private {remoting.AutoReconnector} */ |
| this.reconnector_ = null; |
| |
| /** @private {remoting.SessionLogger} */ |
| this.logger_ = null; |
| |
| /** @private {remoting.DesktopRemotingActivity} */ |
| this.desktopActivity_ = null; |
| |
| /** @private {remoting.GnubbyAuthHandler} */ |
| this.gnubbyAuthHandler_ = new remoting.GnubbyAuthHandler(); |
| |
| /** @private {Array<string>} */ |
| this.additionalCapabilities_ = []; |
| }; |
| |
| remoting.Me2MeActivity.prototype.dispose = function() { |
| base.dispose(this.desktopActivity_); |
| this.desktopActivity_ = null; |
| base.dispose(this.reconnector_); |
| this.reconnector_ = null; |
| }; |
| |
| remoting.Me2MeActivity.prototype.start = function() { |
| var webappVersion = chrome.runtime.getManifest().version; |
| var that = this; |
| |
| var Event = remoting.ChromotingEvent; |
| this.logger_ = this.createLogger_(Event.SessionEntryPoint.CONNECT_BUTTON); |
| this.logger_.logSessionStateChange(Event.SessionState.STARTED); |
| |
| function handleError(/** remoting.Error */ error) { |
| if (error.isCancel()) { |
| remoting.setMode(remoting.AppMode.HOME); |
| that.logger_.logSessionStateChange( |
| Event.SessionState.CONNECTION_CANCELED); |
| } else { |
| that.logger_.logSessionStateChange( |
| Event.SessionState.CONNECTION_FAILED, error); |
| that.showErrorMessage_(error); |
| } |
| } |
| |
| this.hostUpdateDialog_.showIfNecessary(webappVersion).catch( |
| remoting.Error.handler(function(/** remoting.Error */ error) { |
| // User cancels the Host upgrade dialog. Report it as bad version. |
| throw new remoting.Error(remoting.Error.Tag.BAD_VERSION); |
| })).then( |
| this.gnubbyAuthHandler_.isGnubbyExtensionInstalled.bind( |
| this.gnubbyAuthHandler_) |
| ).then( |
| function (extensionInstalled) { |
| if (extensionInstalled) { |
| this.additionalCapabilities_.push( |
| remoting.ClientSession.Capability.SECURITY_KEY); |
| } |
| }.bind(this) |
| ).then( |
| this.connect_.bind(this) |
| ).catch(remoting.Error.handler(handleError)); |
| }; |
| |
| remoting.Me2MeActivity.prototype.stop = function() { |
| if (this.desktopActivity_) { |
| this.desktopActivity_.stop(); |
| } |
| }; |
| |
| /** @return {remoting.DesktopRemotingActivity} */ |
| remoting.Me2MeActivity.prototype.getDesktopActivity = function() { |
| return this.desktopActivity_; |
| }; |
| |
| /** |
| * @param {remoting.ChromotingEvent.SessionEntryPoint} entryPoint |
| * @return {!remoting.SessionLogger} |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.createLogger_ = function(entryPoint) { |
| var Mode = remoting.ChromotingEvent.Mode; |
| var logger = remoting.SessionLogger.createForClient(); |
| logger.setEntryPoint(entryPoint); |
| logger.setHost(this.host_); |
| logger.setLogEntryMode(Mode.ME2ME); |
| logger.setHostStatusUpdateElapsedTime( |
| this.hostList_.getHostStatusUpdateElapsedTime()); |
| return logger; |
| }; |
| |
| /** |
| * @param {remoting.ChromotingEvent.SessionEntryPoint} entryPoint |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.reconnect_ = function(entryPoint) { |
| console.assert(this.logger_, 'Reconnecting without a previous session.'); |
| var previousSessionSummary = this.logger_.createSummary(); |
| this.logger_ = this.createLogger_(entryPoint); |
| this.logger_.setPreviousSessionSummary(previousSessionSummary); |
| var Event = remoting.ChromotingEvent; |
| this.logger_.logSessionStateChange(Event.SessionState.STARTED); |
| this.connect_(); |
| }; |
| |
| /** |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.connect_ = function() { |
| base.dispose(this.desktopActivity_); |
| |
| this.desktopActivity_ = new remoting.DesktopRemotingActivity( |
| this, this.logger_, this.additionalCapabilities_); |
| this.desktopActivity_.getConnectingDialog().show(); |
| this.host_.options.load().then( |
| function() { |
| this.desktopActivity_.start(this.host_, |
| this.createCredentialsProvider_()); |
| }.bind(this)); |
| }; |
| |
| /** |
| * @return {remoting.CredentialsProvider} |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.createCredentialsProvider_ = function() { |
| var host = this.host_; |
| var that = this; |
| |
| /** |
| * @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) { |
| var thirdPartyTokenFetcher = new remoting.ThirdPartyTokenFetcher( |
| tokenUrl, hostPublicKey, scope, host.tokenUrlPatterns, |
| onThirdPartyTokenFetched); |
| thirdPartyTokenFetcher.fetchToken(); |
| }; |
| |
| /** |
| * @param {boolean} supportsPairing |
| * @param {function(string):void} onPinFetched |
| */ |
| var requestPin = function(supportsPairing, onPinFetched) { |
| // Set time when PIN was requested. |
| var authStartTime = Date.now(); |
| that.desktopActivity_.getConnectingDialog().hide(); |
| that.pinDialog_.show(supportsPairing).then(function(/** string */ pin) { |
| remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); |
| // Done obtaining PIN information. Log time taken for PIN entry. |
| that.logger_.setAuthTotalTime(Date.now() - authStartTime); |
| onPinFetched(pin); |
| }).catch(remoting.Error.handler(function(/** remoting.Error */ error) { |
| console.assert(error.hasTag(remoting.Error.Tag.CANCELLED), |
| 'Unexpected error: ' + error.getTag() + '.'); |
| remoting.setMode(remoting.AppMode.HOME); |
| that.stop(); |
| })); |
| }; |
| |
| return new remoting.CredentialsProvider({ |
| fetchPin: requestPin, |
| pairingInfo: host.options.getPairingInfo(), |
| fetchThirdPartyToken: fetchThirdPartyToken |
| }); |
| }; |
| |
| /** |
| * @param {!remoting.Error} error |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.reconnectOnHostOffline_ = function(error) { |
| var that = this; |
| var onHostListRefresh = function() { |
| var host = that.hostList_.getHostForId(that.host_.hostId); |
| var SessionEntryPoint = remoting.ChromotingEvent.SessionEntryPoint; |
| |
| // Reconnect if the host's JID changes. |
| if (Boolean(host) && |
| host.jabberId != that.host_.jabberId && |
| host.status == 'ONLINE') { |
| that.host_ = host; |
| that.reconnect_(SessionEntryPoint.AUTO_RECONNECT_ON_HOST_OFFLINE); |
| return; |
| } |
| that.showErrorMessage_(error); |
| }; |
| |
| this.retryOnHostOffline_ = false; |
| // The plugin will be re-created when the host list has been refreshed. |
| this.hostList_.refreshAndDisplay().then( |
| onHostListRefresh |
| ).catch( |
| this.showErrorMessage_.bind(this, error) |
| ); |
| }; |
| |
| /** |
| * @param {!remoting.Error} error |
| */ |
| remoting.Me2MeActivity.prototype.onConnectionFailed = function(error) { |
| base.dispose(this.desktopActivity_); |
| this.desktopActivity_ = null; |
| |
| if (error.isNone()) { |
| // This could happen if the host terminates the session before it is |
| // connected. |
| this.showFinishDialog_(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME); |
| } else if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) && |
| this.retryOnHostOffline_) { |
| this.reconnectOnHostOffline_(error); |
| } else { |
| this.showErrorMessage_(error); |
| } |
| }; |
| |
| /** |
| * @param {!remoting.ConnectionInfo} connectionInfo |
| */ |
| remoting.Me2MeActivity.prototype.onConnected = function(connectionInfo) { |
| // Reset the refresh flag so that the next connection will retry if needed. |
| this.retryOnHostOffline_ = true; |
| |
| var plugin = connectionInfo.plugin(); |
| // TODO(joedow): Do not register the GnubbyAuthHandler extension if the host |
| // does not support security key forwarding. |
| plugin.extensions().register(this.gnubbyAuthHandler_); |
| this.pinDialog_.requestPairingIfNecessary(plugin); |
| |
| // Drop the session after 30s of suspension. If this timeout is too short, we |
| // risk dropping a connection that is self-recoverable. If this timeout is too |
| // long, the user may lose their patience and just manually reconnect. |
| this.desktopActivity_.getSession().dropSessionOnSuspend(30 * 1000); |
| }; |
| |
| remoting.Me2MeActivity.prototype.onDisconnected = function(error) { |
| base.dispose(this.desktopActivity_); |
| this.desktopActivity_ = null; |
| |
| if (error.isNone()) { |
| this.showFinishDialog_(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME); |
| } else if (remoting.AutoReconnector.shouldAutoReconnect(error)) { |
| var SessionEntryPoint = remoting.ChromotingEvent.SessionEntryPoint; |
| this.reconnector_ = new remoting.AutoReconnector( |
| this.reconnect_.bind( |
| this, SessionEntryPoint.AUTO_RECONNECT_ON_CONNECTION_DROPPED)); |
| } else { |
| this.showErrorMessage_(error); |
| } |
| }; |
| |
| /** |
| * @param {!remoting.Error} error |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.showErrorMessage_ = function(error) { |
| var errorDiv = document.getElementById('connect-error-message'); |
| l10n.localizeElementFromTag(errorDiv, error.getTag()); |
| this.showFinishDialog_(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME); |
| }; |
| |
| /** |
| * @param {remoting.AppMode} mode |
| * @private |
| */ |
| remoting.Me2MeActivity.prototype.showFinishDialog_ = function(mode) { |
| var dialog = remoting.modalDialogFactory.createMessageDialog( |
| mode, |
| base.getHtmlElement('client-finished-me2me-button'), |
| base.getHtmlElement('client-reconnect-button')); |
| |
| /** @typedef {remoting.MessageDialog.Result} */ |
| var Result = remoting.MessageDialog.Result; |
| var that = this; |
| |
| dialog.show().then(function(/** Result */result) { |
| base.dispose(that.reconnector_); |
| that.reconnector_ = null; |
| |
| if (result === Result.PRIMARY) { |
| remoting.setMode(remoting.AppMode.HOME); |
| } else { |
| that.reconnect_( |
| remoting.ChromotingEvent.SessionEntryPoint.RECONNECT_BUTTON); |
| } |
| }); |
| }; |
| |
| /** |
| * @param {HTMLElement} rootElement |
| * @param {remoting.Host} host |
| * @constructor |
| */ |
| remoting.HostNeedsUpdateDialog = function(rootElement, host) { |
| /** @private */ |
| this.host_ = host; |
| /** @private */ |
| this.dialog_ = remoting.modalDialogFactory.createMessageDialog( |
| remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE, |
| rootElement.querySelector('.connect-button'), |
| rootElement.querySelector('.cancel-button')); |
| |
| l10n.localizeElementFromTag( |
| rootElement.querySelector('.host-needs-update-message'), |
| /*i18n-content*/'HOST_NEEDS_UPDATE_TITLE', host.hostName); |
| }; |
| |
| /** |
| * Shows the HostNeedsUpdateDialog if the user is trying to connect to a legacy |
| * host. |
| * |
| * @param {string} webappVersion |
| * @return {Promise} Promise that resolves when no update is required or |
| * when the user ignores the update warning. Rejects with |
| * |remoting.Error.CANCELLED| if the user cancels the connection. |
| */ |
| remoting.HostNeedsUpdateDialog.prototype.showIfNecessary = |
| function(webappVersion) { |
| if (!remoting.Host.needsUpdate(this.host_, webappVersion)) { |
| return Promise.resolve(); |
| } |
| /** @typedef {remoting.MessageDialog.Result} */ |
| var Result = remoting.MessageDialog.Result; |
| return this.dialog_.show().then(function(/** Result */ result) { |
| if (result === Result.SECONDARY) { |
| return Promise.reject(new remoting.Error(remoting.Error.Tag.CANCELLED)); |
| } |
| }); |
| }; |
| |
| /** |
| * @param {HTMLElement} rootElement |
| * @param {remoting.Host} host |
| * @constructor |
| */ |
| remoting.PinDialog = function(rootElement, host) { |
| /** @private */ |
| this.rootElement_ = rootElement; |
| /** @private */ |
| this.pairingCheckbox_ = rootElement.querySelector('.pairing-checkbox'); |
| /** @private */ |
| this.pinInput_ = rootElement.querySelector('.pin-inputField'); |
| /** @private */ |
| this.host_ = host; |
| /** @private */ |
| this.dialog_ = remoting.modalDialogFactory.createInputDialog( |
| remoting.AppMode.CLIENT_PIN_PROMPT, |
| this.rootElement_.querySelector('form'), |
| this.pinInput_, |
| this.rootElement_.querySelector('.cancel-button')); |
| }; |
| |
| |
| /** |
| * @param {boolean} supportsPairing |
| * @return {Promise<string>} Promise that resolves with the PIN or rejects with |
| * |remoting.Error.CANCELLED| if the user cancels the connection. |
| */ |
| remoting.PinDialog.prototype.show = function(supportsPairing) { |
| // Reset the UI. |
| this.pairingCheckbox_.checked = false; |
| this.rootElement_.querySelector('.pairing-section').hidden = !supportsPairing; |
| var message = this.rootElement_.querySelector('.pin-message'); |
| l10n.localizeElement(message, this.host_.hostName); |
| this.pinInput_.value = ''; |
| return this.dialog_.show(); |
| }; |
| |
| /** |
| * @param {remoting.ClientPlugin} plugin |
| */ |
| remoting.PinDialog.prototype.requestPairingIfNecessary = function(plugin) { |
| if (this.pairingCheckbox_.checked) { |
| var that = this; |
| /** |
| * @param {string} clientId |
| * @param {string} sharedSecret |
| */ |
| var onPairingComplete = function(clientId, sharedSecret) { |
| that.host_.options.setPairingInfo({'clientId': clientId, |
| 'sharedSecret': sharedSecret}); |
| that.host_.options.save(); |
| }; |
| |
| // Use the platform name as a proxy for the local computer name. |
| // TODO(jamiewalch): Use a descriptive name for the local computer, for |
| // example, its Chrome Sync name. |
| var clientName = ''; |
| if (remoting.platformIsMac()) { |
| clientName = 'Mac'; |
| } else if (remoting.platformIsWindows()) { |
| clientName = 'Windows'; |
| } else if (remoting.platformIsChromeOS()) { |
| clientName = 'ChromeOS'; |
| } else if (remoting.platformIsLinux()) { |
| clientName = 'Linux'; |
| } else { |
| console.log('Unrecognized client platform. Using navigator.platform.'); |
| clientName = navigator.platform; |
| } |
| plugin.requestPairing(clientName, onPairingComplete); |
| } |
| }; |
| |
| /** |
| * A helper class to handle auto reconnect when the connection is dropped due to |
| * client connectivity issues. |
| * |
| * @param {Function} reconnectCallback callback to initiate the reconnect |
| * |
| * @constructor |
| * @implements {base.Disposable} |
| * |
| * @private |
| */ |
| remoting.AutoReconnector = function(reconnectCallback) { |
| /** @private */ |
| this.reconnectCallback_ = reconnectCallback; |
| /** @private */ |
| this.networkDetector_ = remoting.NetworkConnectivityDetector.create(); |
| /** @private */ |
| this.connectingDialog_ = remoting.modalDialogFactory.createConnectingDialog( |
| this.dispose.bind(this)); |
| |
| var that = this; |
| this.connectingDialog_.show(); |
| this.networkDetector_.waitForOnline().then(function() { |
| if (that.reconnectCallback_) { |
| that.connectingDialog_.hide(); |
| that.reconnectCallback_(); |
| } |
| }); |
| }; |
| |
| remoting.AutoReconnector.shouldAutoReconnect = function(error) { |
| return error.hasTag(remoting.Error.Tag.CLIENT_SUSPENDED); |
| }; |
| |
| remoting.AutoReconnector.prototype.dispose = function() { |
| base.dispose(this.networkDetector_); |
| this.networkDetector_ = null; |
| this.reconnectCallback_ = null; |
| this.connectingDialog_.hide(); |
| }; |
| |
| })(); |