1 // Copyright 2015 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 /** @suppress {duplicate} */
6 var remoting = remoting || {};
13 * @param {remoting.Host} host
14 * @param {remoting.HostList} hostList
17 * @implements {remoting.Activity}
19 remoting.Me2MeActivity = function(host, hostList) {
23 this.hostList_ = hostList;
26 new remoting.PinDialog(document.getElementById('pin-dialog'), host);
28 this.hostUpdateDialog_ = new remoting.HostNeedsUpdateDialog(
29 document.getElementById('host-needs-update-dialog'), this.host_);
32 this.retryOnHostOffline_ = true;
34 /** @private {remoting.SmartReconnector} */
35 this.reconnector_ = null;
37 /** @private {remoting.SessionLogger} */
40 /** @private {remoting.DesktopRemotingActivity} */
41 this.desktopActivity_ = null;
44 remoting.Me2MeActivity.prototype.dispose = function() {
45 base.dispose(this.desktopActivity_);
46 this.desktopActivity_ = null;
49 remoting.Me2MeActivity.prototype.start = function() {
50 var webappVersion = chrome.runtime.getManifest().version;
53 var Event = remoting.ChromotingEvent;
54 this.logger_ = this.createLogger_(Event.SessionEntryPoint.CONNECT_BUTTON);
55 this.logger_.logSessionStateChange(Event.SessionState.STARTED,
56 Event.ConnectionError.NONE);
58 this.hostUpdateDialog_.showIfNecessary(webappVersion).then(function() {
59 return that.host_.options.load();
62 }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
63 if (error.hasTag(remoting.Error.Tag.CANCELLED)) {
64 remoting.setMode(remoting.AppMode.HOME);
69 remoting.Me2MeActivity.prototype.stop = function() {
70 if (this.desktopActivity_) {
71 this.desktopActivity_.stop();
75 /** @return {remoting.DesktopRemotingActivity} */
76 remoting.Me2MeActivity.prototype.getDesktopActivity = function() {
77 return this.desktopActivity_;
81 * @param {remoting.ChromotingEvent.SessionEntryPoint} entryPoint
82 * @return {!remoting.SessionLogger}
85 remoting.Me2MeActivity.prototype.createLogger_ = function(entryPoint) {
86 var Mode = remoting.ChromotingEvent.Mode;
87 var logger = remoting.SessionLogger.createForClient();
88 logger.setEntryPoint(entryPoint);
89 logger.setHostVersion(this.host_.hostVersion);
90 logger.setLogEntryMode(Mode.ME2ME);
95 * @param {remoting.ChromotingEvent.SessionEntryPoint} entryPoint
98 remoting.Me2MeActivity.prototype.reconnect_ = function(entryPoint) {
99 this.logger_ = this.createLogger_(entryPoint);
100 var Event = remoting.ChromotingEvent;
101 this.logger_.logSessionStateChange(Event.SessionState.STARTED,
102 Event.ConnectionError.NONE);
109 remoting.Me2MeActivity.prototype.connect_ = function() {
110 base.dispose(this.desktopActivity_);
111 this.desktopActivity_ = new remoting.DesktopRemotingActivity(this);
112 this.desktopActivity_.getConnectingDialog().show();
113 this.desktopActivity_.start(this.host_, this.createCredentialsProvider_(),
118 * @return {remoting.CredentialsProvider}
121 remoting.Me2MeActivity.prototype.createCredentialsProvider_ = function() {
122 var host = this.host_;
126 * @param {string} tokenUrl Token-issue URL received from the host.
127 * @param {string} hostPublicKey Host public key (DER and Base64 encoded).
128 * @param {string} scope OAuth scope to request the token for.
129 * @param {function(string, string):void} onThirdPartyTokenFetched Callback.
131 var fetchThirdPartyToken = function(
132 tokenUrl, hostPublicKey, scope, onThirdPartyTokenFetched) {
133 var thirdPartyTokenFetcher = new remoting.ThirdPartyTokenFetcher(
134 tokenUrl, hostPublicKey, scope, host.tokenUrlPatterns,
135 onThirdPartyTokenFetched);
136 thirdPartyTokenFetcher.fetchToken();
140 * @param {boolean} supportsPairing
141 * @param {function(string):void} onPinFetched
143 var requestPin = function(supportsPairing, onPinFetched) {
144 // Set time when PIN was requested.
145 var authStartTime = new Date().getTime();
146 that.desktopActivity_.getConnectingDialog().hide();
147 that.pinDialog_.show(supportsPairing).then(function(/** string */ pin) {
148 remoting.setMode(remoting.AppMode.CLIENT_CONNECTING);
149 // Done obtaining PIN information. Log time taken for PIN entry.
150 that.logger_.setAuthTotalTime(new Date().getTime() - authStartTime);
152 }).catch(remoting.Error.handler(function(/** remoting.Error */ error) {
153 console.assert(error.hasTag(remoting.Error.Tag.CANCELLED),
154 'Unexpected error: ' + error.getTag() + '.');
155 remoting.setMode(remoting.AppMode.HOME);
160 return new remoting.CredentialsProvider({
161 fetchPin: requestPin,
162 pairingInfo: /** @type{remoting.PairingInfo} */ (
163 base.deepCopy(host.options.pairingInfo)),
164 fetchThirdPartyToken: fetchThirdPartyToken
169 * @param {!remoting.Error} error
172 remoting.Me2MeActivity.prototype.reconnectOnHostOffline_ = function(error) {
174 var onHostListRefresh = function(/** boolean */ success) {
176 var host = that.hostList_.getHostForId(that.host_.hostId);
177 var SessionEntryPoint = remoting.ChromotingEvent.SessionEntryPoint;
179 // Reconnect if the host's JID changes.
180 if (Boolean(host) && host.jabberId != that.host_.jabberId) {
182 that.reconnect_(SessionEntryPoint.AUTO_RECONNECT_ON_HOST_OFFLINE);
186 that.showErrorMessage_(error);
189 this.retryOnHostOffline_ = false;
190 // The plugin will be re-created when the host list has been refreshed.
191 this.hostList_.refresh(onHostListRefresh);
195 * @param {!remoting.Error} error
197 remoting.Me2MeActivity.prototype.onConnectionFailed = function(error) {
198 base.dispose(this.desktopActivity_);
199 this.desktopActivity_ = null;
201 if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) &&
202 this.retryOnHostOffline_) {
203 this.reconnectOnHostOffline_(error);
204 } else if (!error.isNone()) {
205 this.showErrorMessage_(error);
210 * @param {!remoting.ConnectionInfo} connectionInfo
212 remoting.Me2MeActivity.prototype.onConnected = function(connectionInfo) {
213 // Reset the refresh flag so that the next connection will retry if needed.
214 this.retryOnHostOffline_ = true;
216 var plugin = connectionInfo.plugin();
217 if (plugin.hasCapability(remoting.ClientSession.Capability.CAST)) {
218 plugin.extensions().register(new remoting.CastExtensionHandler());
220 plugin.extensions().register(new remoting.GnubbyAuthHandler());
221 this.pinDialog_.requestPairingIfNecessary(connectionInfo.plugin());
223 var SessionEntryPoint = remoting.ChromotingEvent.SessionEntryPoint;
225 base.dispose(this.reconnector_);
226 this.reconnector_ = new remoting.SmartReconnector(
227 this.desktopActivity_.getConnectingDialog(),
228 this.reconnect_.bind(
229 this, SessionEntryPoint.AUTO_RECONNECT_ON_CONNECTION_DROPPED),
230 this.stop.bind(this), connectionInfo.session());
233 remoting.Me2MeActivity.prototype.onDisconnected = function(error) {
234 if (error.isNone()) {
235 this.showFinishDialog_(remoting.AppMode.CLIENT_SESSION_FINISHED_ME2ME);
237 this.reconnector_.onConnectionDropped(error);
238 this.showErrorMessage_(error);
241 base.dispose(this.desktopActivity_);
242 this.desktopActivity_ = null;
246 * @param {!remoting.Error} error
249 remoting.Me2MeActivity.prototype.showErrorMessage_ = function(error) {
250 var errorDiv = document.getElementById('connect-error-message');
251 l10n.localizeElementFromTag(errorDiv, error.getTag());
252 this.showFinishDialog_(remoting.AppMode.CLIENT_CONNECT_FAILED_ME2ME);
256 * @param {remoting.AppMode} mode
259 remoting.Me2MeActivity.prototype.showFinishDialog_ = function(mode) {
260 var dialog = remoting.modalDialogFactory.createMessageDialog(
262 document.getElementById('client-finished-me2me-button'),
263 document.getElementById('client-reconnect-button'));
265 /** @typedef {remoting.MessageDialog.Result} */
266 var Result = remoting.MessageDialog.Result;
269 dialog.show().then(function(/** Result */result) {
270 if (result === Result.PRIMARY) {
271 remoting.setMode(remoting.AppMode.HOME);
274 remoting.ChromotingEvent.SessionEntryPoint.RECONNECT_BUTTON);
280 * @param {HTMLElement} rootElement
281 * @param {remoting.Host} host
284 remoting.HostNeedsUpdateDialog = function(rootElement, host) {
288 this.dialog_ = remoting.modalDialogFactory.createMessageDialog(
289 remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE,
290 rootElement.querySelector('.connect-button'),
291 rootElement.querySelector('.cancel-button'));
293 l10n.localizeElementFromTag(
294 rootElement.querySelector('.host-needs-update-message'),
295 /*i18n-content*/'HOST_NEEDS_UPDATE_TITLE', host.hostName);
299 * Shows the HostNeedsUpdateDialog if the user is trying to connect to a legacy
302 * @param {string} webappVersion
303 * @return {Promise} Promise that resolves when no update is required or
304 * when the user ignores the update warning. Rejects with
305 * |remoting.Error.CANCELLED| if the user cancels the connection.
307 remoting.HostNeedsUpdateDialog.prototype.showIfNecessary =
308 function(webappVersion) {
309 if (!remoting.Host.needsUpdate(this.host_, webappVersion)) {
310 return Promise.resolve();
312 /** @typedef {remoting.MessageDialog.Result} */
313 var Result = remoting.MessageDialog.Result;
314 return this.dialog_.show().then(function(/** Result */ result) {
315 if (result === Result.SECONDARY) {
316 return Promise.reject(new remoting.Error(remoting.Error.Tag.CANCELLED));
322 * @param {HTMLElement} rootElement
323 * @param {remoting.Host} host
326 remoting.PinDialog = function(rootElement, host) {
328 this.rootElement_ = rootElement;
330 this.pairingCheckbox_ = rootElement.querySelector('.pairing-checkbox');
332 this.pinInput_ = rootElement.querySelector('.pin-inputField');
336 this.dialog_ = remoting.modalDialogFactory.createInputDialog(
337 remoting.AppMode.CLIENT_PIN_PROMPT,
338 this.rootElement_.querySelector('form'),
340 this.rootElement_.querySelector('.cancel-button'));
345 * @param {boolean} supportsPairing
346 * @return {Promise<string>} Promise that resolves with the PIN or rejects with
347 * |remoting.Error.CANCELLED| if the user cancels the connection.
349 remoting.PinDialog.prototype.show = function(supportsPairing) {
351 this.pairingCheckbox_.checked = false;
352 this.rootElement_.querySelector('.pairing-section').hidden = !supportsPairing;
353 var message = this.rootElement_.querySelector('.pin-message');
354 l10n.localizeElement(message, this.host_.hostName);
355 this.pinInput_.value = '';
356 return this.dialog_.show();
360 * @param {remoting.ClientPlugin} plugin
362 remoting.PinDialog.prototype.requestPairingIfNecessary = function(plugin) {
363 if (this.pairingCheckbox_.checked) {
366 * @param {string} clientId
367 * @param {string} sharedSecret
369 var onPairingComplete = function(clientId, sharedSecret) {
370 that.host_.options.pairingInfo.clientId = clientId;
371 that.host_.options.pairingInfo.sharedSecret = sharedSecret;
372 that.host_.options.save();
375 // Use the platform name as a proxy for the local computer name.
376 // TODO(jamiewalch): Use a descriptive name for the local computer, for
377 // example, its Chrome Sync name.
379 if (remoting.platformIsMac()) {
381 } else if (remoting.platformIsWindows()) {
382 clientName = 'Windows';
383 } else if (remoting.platformIsChromeOS()) {
384 clientName = 'ChromeOS';
385 } else if (remoting.platformIsLinux()) {
386 clientName = 'Linux';
388 console.log('Unrecognized client platform. Using navigator.platform.');
389 clientName = navigator.platform;
391 plugin.requestPairing(clientName, onPairingComplete);