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

Side by Side Diff: remoting/webapp/crd/js/client_session.js

Issue 1133913002: [Chromoting] Move shared webapp JS files from crd/js -> base/js (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 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
(Empty)
1 // Copyright (c) 2012 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.
4
5 /**
6 * @fileoverview
7 * Class handling creation and teardown of a remoting client session.
8 *
9 * The ClientSession class controls lifetime of the client plugin
10 * object and provides the plugin with the functionality it needs to
11 * establish connection, e.g. delivers incoming/outgoing signaling
12 * messages.
13 *
14 * This class should not access the plugin directly, instead it should
15 * do it through ClientPlugin class which abstracts plugin version
16 * differences.
17 */
18
19 'use strict';
20
21 /** @suppress {duplicate} */
22 var remoting = remoting || {};
23
24 /**
25 * @param {remoting.ClientPlugin} plugin
26 * @param {remoting.SignalStrategy} signalStrategy Signal strategy.
27 * @param {remoting.ClientSession.EventHandler} listener
28 *
29 * @constructor
30 * @extends {base.EventSourceImpl}
31 * @implements {base.Disposable}
32 * @implements {remoting.ClientPlugin.ConnectionEventHandler}
33 */
34 remoting.ClientSession = function(plugin, signalStrategy, listener) {
35 base.inherits(this, base.EventSourceImpl);
36
37 /** @private */
38 this.state_ = remoting.ClientSession.State.INITIALIZING;
39
40 /** @private {!remoting.Error} */
41 this.error_ = remoting.Error.none();
42
43 /** @private {remoting.Host} */
44 this.host_ = null;
45
46 /** @private {remoting.CredentialsProvider} */
47 this.credentialsProvider_ = null;
48
49 /** @private */
50 this.sessionId_ = '';
51
52 /** @private */
53 this.listener_ = listener;
54
55 /** @private */
56 this.hasReceivedFrame_ = false;
57
58 /** @private */
59 this.logToServer_ = new remoting.LogToServer(signalStrategy);
60
61 /** @private */
62 this.signalStrategy_ = signalStrategy;
63 base.debug.assert(this.signalStrategy_.getState() ==
64 remoting.SignalStrategy.State.CONNECTED);
65 this.signalStrategy_.setIncomingStanzaCallback(
66 this.onIncomingMessage_.bind(this));
67
68 /** @private {remoting.FormatIq} */
69 this.iqFormatter_ = null;
70
71 /**
72 * Allow host-offline error reporting to be suppressed in situations where it
73 * would not be useful, for example, when using a cached host JID.
74 *
75 * @type {boolean} @private
76 */
77 this.logHostOfflineErrors_ = true;
78
79 /** @private {remoting.ClientPlugin} */
80 this.plugin_ = plugin;
81 plugin.setConnectionEventHandler(this);
82
83 /** @private */
84 this.connectedDisposables_ = new base.Disposables();
85
86 this.defineEvents(Object.keys(remoting.ClientSession.Events));
87 };
88
89 /** @enum {string} */
90 remoting.ClientSession.Events = {
91 videoChannelStateChanged: 'videoChannelStateChanged'
92 };
93
94 /**
95 * @interface
96 * [START]-------> [onConnected] ------> [onDisconnected]
97 * |
98 * |-----> [OnConnectionFailed]
99 *
100 */
101 remoting.ClientSession.EventHandler = function() {};
102
103 /**
104 * Called when the connection failed before it is connected.
105 *
106 * @param {!remoting.Error} error
107 */
108 remoting.ClientSession.EventHandler.prototype.onConnectionFailed =
109 function(error) {};
110
111 /**
112 * Called when a new session has been connected. The |connectionInfo| will be
113 * valid until onDisconnected() is called.
114 *
115 * @param {!remoting.ConnectionInfo} connectionInfo
116 */
117 remoting.ClientSession.EventHandler.prototype.onConnected =
118 function(connectionInfo) {};
119
120 /**
121 * Called when the current session has been disconnected.
122 *
123 * @param {!remoting.Error} reason Reason that the session is disconnected.
124 * Set to remoting.Error.none() if there is no error.
125 */
126 remoting.ClientSession.EventHandler.prototype.onDisconnected =
127 function(reason) {};
128
129 // Note that the positive values in both of these enums are copied directly
130 // from connection_to_host.h and must be kept in sync. Code in
131 // chromoting_instance.cc converts the C++ enums into strings that must match
132 // the names given here.
133 // The negative values represent state transitions that occur within the
134 // web-app that have no corresponding plugin state transition.
135 /** @enum {number} */
136 remoting.ClientSession.State = {
137 CONNECTION_CANCELED: -3, // Connection closed (gracefully) before connecting.
138 CONNECTION_DROPPED: -2, // Succeeded, but subsequently closed with an error.
139 CREATED: -1,
140 UNKNOWN: 0,
141 INITIALIZING: 1,
142 CONNECTING: 2,
143 // We don't currently receive AUTHENTICATED from the host - it comes through
144 // as 'CONNECTING' instead.
145 // TODO(garykac) Update chromoting_instance.cc to send this once we've
146 // shipped a webapp release with support for AUTHENTICATED.
147 AUTHENTICATED: 3,
148 CONNECTED: 4,
149 CLOSED: 5,
150 FAILED: 6
151 };
152
153 /**
154 * @param {string} state The state name.
155 * @return {remoting.ClientSession.State} The session state enum value.
156 */
157 remoting.ClientSession.State.fromString = function(state) {
158 if (!remoting.ClientSession.State.hasOwnProperty(state)) {
159 throw "Invalid ClientSession.State: " + state;
160 }
161 return remoting.ClientSession.State[state];
162 };
163
164 /** @enum {number} */
165 remoting.ClientSession.ConnectionError = {
166 UNKNOWN: -1,
167 NONE: 0,
168 HOST_IS_OFFLINE: 1,
169 SESSION_REJECTED: 2,
170 INCOMPATIBLE_PROTOCOL: 3,
171 NETWORK_FAILURE: 4,
172 HOST_OVERLOAD: 5
173 };
174
175 /**
176 * @param {string} error The connection error name.
177 * @return {remoting.ClientSession.ConnectionError} The connection error enum.
178 */
179 remoting.ClientSession.ConnectionError.fromString = function(error) {
180 if (!remoting.ClientSession.ConnectionError.hasOwnProperty(error)) {
181 console.error('Unexpected ClientSession.ConnectionError string: ', error);
182 return remoting.ClientSession.ConnectionError.UNKNOWN;
183 }
184 return remoting.ClientSession.ConnectionError[error];
185 }
186
187 /**
188 * Type used for performance statistics collected by the plugin.
189 * @constructor
190 */
191 remoting.ClientSession.PerfStats = function() {};
192 /** @type {number} */
193 remoting.ClientSession.PerfStats.prototype.videoBandwidth;
194 /** @type {number} */
195 remoting.ClientSession.PerfStats.prototype.videoFrameRate;
196 /** @type {number} */
197 remoting.ClientSession.PerfStats.prototype.captureLatency;
198 /** @type {number} */
199 remoting.ClientSession.PerfStats.prototype.encodeLatency;
200 /** @type {number} */
201 remoting.ClientSession.PerfStats.prototype.decodeLatency;
202 /** @type {number} */
203 remoting.ClientSession.PerfStats.prototype.renderLatency;
204 /** @type {number} */
205 remoting.ClientSession.PerfStats.prototype.roundtripLatency;
206
207 // Keys for connection statistics.
208 remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH = 'videoBandwidth';
209 remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE = 'videoFrameRate';
210 remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY = 'captureLatency';
211 remoting.ClientSession.STATS_KEY_ENCODE_LATENCY = 'encodeLatency';
212 remoting.ClientSession.STATS_KEY_DECODE_LATENCY = 'decodeLatency';
213 remoting.ClientSession.STATS_KEY_RENDER_LATENCY = 'renderLatency';
214 remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY = 'roundtripLatency';
215
216 /**
217 * Set of capabilities for which hasCapability() can be used to test.
218 *
219 * @enum {string}
220 */
221 remoting.ClientSession.Capability = {
222 // When enabled this capability causes the client to send its screen
223 // resolution to the host once connection has been established. See
224 // this.plugin_.notifyClientResolution().
225 SEND_INITIAL_RESOLUTION: 'sendInitialResolution',
226
227 // Let the host know that we're interested in knowing whether or not it
228 // rate limits desktop-resize requests.
229 // TODO(kelvinp): This has been supported since M-29. Currently we only have
230 // <1000 users on M-29 or below. Remove this and the capability on the host.
231 RATE_LIMIT_RESIZE_REQUESTS: 'rateLimitResizeRequests',
232
233 // Indicates that host/client supports Google Drive integration, and that the
234 // client should send to the host the OAuth tokens to be used by Google Drive
235 // on the host.
236 GOOGLE_DRIVE: 'googleDrive',
237
238 // Indicates that the client supports the video frame-recording extension.
239 VIDEO_RECORDER: 'videoRecorder',
240
241 // Indicates that the client supports 'cast'ing the video stream to a
242 // cast-enabled device.
243 CAST: 'casting',
244 };
245
246 /**
247 * Connects to |host| using |credentialsProvider| as the credentails.
248 *
249 * @param {remoting.Host} host
250 * @param {remoting.CredentialsProvider} credentialsProvider
251 */
252 remoting.ClientSession.prototype.connect = function(host, credentialsProvider) {
253 this.host_ = host;
254 this.credentialsProvider_ = credentialsProvider;
255 this.iqFormatter_ =
256 new remoting.FormatIq(this.signalStrategy_.getJid(), host.jabberId);
257 this.plugin_.connect(this.host_, this.signalStrategy_.getJid(),
258 credentialsProvider);
259 };
260
261 /**
262 * Disconnect the current session with a particular |error|. The session will
263 * raise a |stateChanged| event in response to it. The caller should then call
264 * dispose() to remove and destroy the <embed> element.
265 *
266 * @param {!remoting.Error} error The reason for the disconnection. Use
267 * remoting.Error.none() if there is no error.
268 * @return {void} Nothing.
269 */
270 remoting.ClientSession.prototype.disconnect = function(error) {
271 if (this.isFinished()) {
272 // Do not send the session-terminate Iq if disconnect() is already called or
273 // if it is initiated by the host.
274 return;
275 }
276
277 this.sendIq_(
278 '<cli:iq ' +
279 'to="' + this.host_.jabberId + '" ' +
280 'type="set" ' +
281 'id="session-terminate" ' +
282 'xmlns:cli="jabber:client">' +
283 '<jingle ' +
284 'xmlns="urn:xmpp:jingle:1" ' +
285 'action="session-terminate" ' +
286 'sid="' + this.sessionId_ + '">' +
287 '<reason><success/></reason>' +
288 '</jingle>' +
289 '</cli:iq>');
290
291 var state = error.isNone() ?
292 remoting.ClientSession.State.CLOSED :
293 remoting.ClientSession.State.FAILED;
294 this.error_ = error;
295 this.setState_(state);
296 };
297
298 /**
299 * Deletes the <embed> element from the container and disconnects.
300 *
301 * @return {void} Nothing.
302 */
303 remoting.ClientSession.prototype.dispose = function() {
304 base.dispose(this.connectedDisposables_);
305 this.connectedDisposables_ = null;
306 base.dispose(this.plugin_);
307 this.plugin_ = null;
308 };
309
310 /**
311 * @return {remoting.ClientSession.State} The current state.
312 */
313 remoting.ClientSession.prototype.getState = function() {
314 return this.state_;
315 };
316
317 /**
318 * @return {remoting.LogToServer}.
319 */
320 remoting.ClientSession.prototype.getLogger = function() {
321 return this.logToServer_;
322 };
323
324 /**
325 * @return {!remoting.Error} The current error code.
326 */
327 remoting.ClientSession.prototype.getError = function() {
328 return this.error_;
329 };
330
331 /**
332 * Called when the client receives its first frame.
333 *
334 * @return {void} Nothing.
335 */
336 remoting.ClientSession.prototype.onFirstFrameReceived = function() {
337 this.hasReceivedFrame_ = true;
338 };
339
340 /**
341 * @return {boolean} Whether the client has received a video buffer.
342 */
343 remoting.ClientSession.prototype.hasReceivedFrame = function() {
344 return this.hasReceivedFrame_;
345 };
346
347 /**
348 * Sends a signaling message.
349 *
350 * @param {string} message XML string of IQ stanza to send to server.
351 * @return {void} Nothing.
352 * @private
353 */
354 remoting.ClientSession.prototype.sendIq_ = function(message) {
355 // Extract the session id, so we can close the session later.
356 var parser = new DOMParser();
357 var iqNode = parser.parseFromString(message, 'text/xml').firstChild;
358 var jingleNode = iqNode.firstChild;
359 if (jingleNode) {
360 var action = jingleNode.getAttribute('action');
361 if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') {
362 this.sessionId_ = jingleNode.getAttribute('sid');
363 }
364 }
365
366 console.log(base.timestamp() + this.iqFormatter_.prettifySendIq(message));
367 if (this.signalStrategy_.getState() !=
368 remoting.SignalStrategy.State.CONNECTED) {
369 console.log("Message above is dropped because signaling is not connected.");
370 return;
371 }
372
373 this.signalStrategy_.sendMessage(message);
374 };
375
376 /**
377 * @param {string} message XML string of IQ stanza to send to server.
378 */
379 remoting.ClientSession.prototype.onOutgoingIq = function(message) {
380 this.sendIq_(message);
381 };
382
383 /**
384 * @param {string} msg
385 */
386 remoting.ClientSession.prototype.onDebugMessage = function(msg) {
387 console.log('plugin: ' + msg.trimRight());
388 };
389
390 /**
391 * @param {Element} message
392 * @private
393 */
394 remoting.ClientSession.prototype.onIncomingMessage_ = function(message) {
395 if (!this.plugin_) {
396 return;
397 }
398 var formatted = new XMLSerializer().serializeToString(message);
399 console.log(base.timestamp() +
400 this.iqFormatter_.prettifyReceiveIq(formatted));
401 this.plugin_.onIncomingIq(formatted);
402 };
403
404 /**
405 * Callback that the plugin invokes to indicate that the connection
406 * status has changed.
407 *
408 * @param {remoting.ClientSession.State} status The plugin's status.
409 * @param {remoting.ClientSession.ConnectionError} error The plugin's error
410 * state, if any.
411 */
412 remoting.ClientSession.prototype.onConnectionStatusUpdate =
413 function(status, error) {
414 if (status == remoting.ClientSession.State.FAILED) {
415 switch (error) {
416 case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE:
417 this.error_ = new remoting.Error(
418 remoting.Error.Tag.HOST_IS_OFFLINE);
419 break;
420 case remoting.ClientSession.ConnectionError.SESSION_REJECTED:
421 this.error_ = new remoting.Error(
422 remoting.Error.Tag.INVALID_ACCESS_CODE);
423 break;
424 case remoting.ClientSession.ConnectionError.INCOMPATIBLE_PROTOCOL:
425 this.error_ = new remoting.Error(
426 remoting.Error.Tag.INCOMPATIBLE_PROTOCOL);
427 break;
428 case remoting.ClientSession.ConnectionError.NETWORK_FAILURE:
429 this.error_ = new remoting.Error(
430 remoting.Error.Tag.P2P_FAILURE);
431 break;
432 case remoting.ClientSession.ConnectionError.HOST_OVERLOAD:
433 this.error_ = new remoting.Error(
434 remoting.Error.Tag.HOST_OVERLOAD);
435 break;
436 default:
437 this.error_ = remoting.Error.unexpected();
438 }
439 }
440 this.setState_(status);
441 };
442
443 /**
444 * Callback that the plugin invokes to indicate that the connection type for
445 * a channel has changed.
446 *
447 * @param {string} channel The channel name.
448 * @param {string} connectionType The new connection type.
449 * @private
450 */
451 remoting.ClientSession.prototype.onRouteChanged =
452 function(channel, connectionType) {
453 console.log('plugin: Channel ' + channel + ' using ' +
454 connectionType + ' connection.');
455 this.logToServer_.setConnectionType(connectionType);
456 };
457
458 /**
459 * Callback that the plugin invokes to indicate when the connection is
460 * ready.
461 *
462 * @param {boolean} ready True if the connection is ready.
463 */
464 remoting.ClientSession.prototype.onConnectionReady = function(ready) {
465 // TODO(jamiewalch): Currently, the logic for determining whether or not the
466 // connection is available is based solely on whether or not any video frames
467 // have been received recently. which leads to poor UX on slow connections.
468 // Re-enable this once crbug.com/435315 has been fixed.
469 var ignoreVideoChannelState = true;
470 if (ignoreVideoChannelState) {
471 console.log('Video channel ' + (ready ? '' : 'not ') + 'ready.');
472 return;
473 }
474
475 this.raiseEvent(remoting.ClientSession.Events.videoChannelStateChanged,
476 ready);
477 };
478
479 /** @return {boolean} */
480 remoting.ClientSession.prototype.isFinished = function() {
481 var finishedStates = [
482 remoting.ClientSession.State.CLOSED,
483 remoting.ClientSession.State.FAILED,
484 remoting.ClientSession.State.CONNECTION_CANCELED,
485 remoting.ClientSession.State.CONNECTION_DROPPED
486 ];
487 return finishedStates.indexOf(this.getState()) !== -1;
488 };
489 /**
490 * @param {remoting.ClientSession.State} newState The new state for the session.
491 * @return {void} Nothing.
492 * @private
493 */
494 remoting.ClientSession.prototype.setState_ = function(newState) {
495 var oldState = this.state_;
496 this.state_ = this.translateState_(oldState, newState);
497
498 if (newState == remoting.ClientSession.State.CONNECTED) {
499 this.connectedDisposables_.add(
500 new base.RepeatingTimer(this.reportStatistics.bind(this), 1000));
501 } else if (this.isFinished()) {
502 base.dispose(this.connectedDisposables_);
503 this.connectedDisposables_ = null;
504 }
505
506 this.notifyStateChanges_(oldState, this.state_);
507 this.logToServer_.logClientSessionStateChange(this.state_, this.error_);
508 };
509
510 /**
511 * @param {remoting.ClientSession.State} oldState The new state for the session.
512 * @param {remoting.ClientSession.State} newState The new state for the session.
513 * @private
514 */
515 remoting.ClientSession.prototype.notifyStateChanges_ =
516 function(oldState, newState) {
517 /** @type {remoting.Error} */
518 var error;
519 switch (this.state_) {
520 case remoting.ClientSession.State.CONNECTED:
521 console.log('Connection established.');
522 var connectionInfo = new remoting.ConnectionInfo(
523 this.host_, this.credentialsProvider_, this, this.plugin_);
524 this.listener_.onConnected(connectionInfo);
525 break;
526
527 case remoting.ClientSession.State.CONNECTING:
528 remoting.identity.getEmail().then(function(/** string */ email) {
529 console.log('Connecting as ' + email);
530 });
531 break;
532
533 case remoting.ClientSession.State.AUTHENTICATED:
534 console.log('Connection authenticated.');
535 break;
536
537 case remoting.ClientSession.State.INITIALIZING:
538 console.log('Connection initializing .');
539 break;
540
541 case remoting.ClientSession.State.CLOSED:
542 console.log('Connection closed.');
543 this.listener_.onDisconnected(remoting.Error.none());
544 break;
545
546 case remoting.ClientSession.State.CONNECTION_CANCELED:
547 case remoting.ClientSession.State.FAILED:
548 error = this.getError();
549 if (!error.isNone()) {
550 console.error('Connection failed: ' + error.toString());
551 }
552 this.listener_.onConnectionFailed(error);
553 break;
554
555 case remoting.ClientSession.State.CONNECTION_DROPPED:
556 error = this.getError();
557 console.error('Connection dropped: ' + error.toString());
558 this.listener_.onDisconnected(error);
559 break;
560
561 default:
562 console.error('Unexpected client plugin state: ' + newState);
563 }
564 };
565
566 /**
567 * @param {remoting.ClientSession.State} previous
568 * @param {remoting.ClientSession.State} current
569 * @return {remoting.ClientSession.State}
570 * @private
571 */
572 remoting.ClientSession.prototype.translateState_ = function(previous, current) {
573 var State = remoting.ClientSession.State;
574 if (previous == State.CONNECTING || previous == State.AUTHENTICATED) {
575 if (current == State.CLOSED) {
576 return remoting.ClientSession.State.CONNECTION_CANCELED;
577 } else if (current == State.FAILED &&
578 this.error_.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) &&
579 !this.logHostOfflineErrors_) {
580 // The application requested host-offline errors to be suppressed, for
581 // example, because this connection attempt is using a cached host JID.
582 console.log('Suppressing host-offline error.');
583 return State.CONNECTION_CANCELED;
584 }
585 } else if (previous == State.CONNECTED && current == State.FAILED) {
586 return State.CONNECTION_DROPPED;
587 }
588 return current;
589 };
590
591 /** @private */
592 remoting.ClientSession.prototype.reportStatistics = function() {
593 this.logToServer_.logStatistics(this.plugin_.getPerfStats());
594 };
595
596 /**
597 * Enable or disable logging of connection errors due to a host being offline.
598 * For example, if attempting a connection using a cached JID, host-offline
599 * errors should not be logged because the JID will be refreshed and the
600 * connection retried.
601 *
602 * @param {boolean} enable True to log host-offline errors; false to suppress.
603 */
604 remoting.ClientSession.prototype.logHostOfflineErrors = function(enable) {
605 this.logHostOfflineErrors_ = enable;
606 };
607
OLDNEW
« no previous file with comments | « remoting/webapp/crd/js/client_plugin_impl.js ('k') | remoting/webapp/crd/js/client_session_factory.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698