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

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

Issue 9148043: Rename webapp_it2me to remoting_webapp and move it from webapp/me2mom to webapp/ (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: add webapp_it2me back Created 8 years, 11 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 | Annotate | Revision Log
« no previous file with comments | « remoting/webapp/me2mom/client_screen.js ('k') | remoting/webapp/me2mom/cs_oauth2_trampoline.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 * This abstracts a <embed> element and controls the plugin which does the
10 * actual remoting work. There should be no UI code inside this class. It
11 * should be purely thought of as a controller of sorts.
12 */
13
14 'use strict';
15
16 /** @suppress {duplicate} */
17 var remoting = remoting || {};
18
19 /**
20 * @param {string} hostJid The jid of the host to connect to.
21 * @param {string} hostPublicKey The base64 encoded version of the host's
22 * public key.
23 * @param {string} authenticationCode The access code for IT2Me or the
24 * PIN for Me2Me.
25 * @param {string} email The username for the talk network.
26 * @param {function(remoting.ClientSession.State,
27 remoting.ClientSession.State):void} onStateChange
28 * The callback to invoke when the session changes state.
29 * @constructor
30 */
31 remoting.ClientSession = function(hostJid, hostPublicKey, authenticationCode,
32 email, onStateChange) {
33 this.state = remoting.ClientSession.State.CREATED;
34
35 this.hostJid = hostJid;
36 this.hostPublicKey = hostPublicKey;
37 this.authenticationCode = authenticationCode;
38 this.email = email;
39 this.clientJid = '';
40 this.sessionId = '';
41 /** @type {remoting.ViewerPlugin} */
42 this.plugin = null;
43 this.logToServer = new remoting.LogToServer();
44 this.onStateChange = onStateChange;
45 /** @type {remoting.ClientSession} */
46 var that = this;
47 /** @type {function():void} @private */
48 this.refocusPlugin_ = function() { that.plugin.focus(); };
49 };
50
51 // Note that the positive values in both of these enums are copied directly
52 // from chromoting_scriptable_object.h and must be kept in sync. The negative
53 // values represent states transitions that occur within the web-app that have
54 // no corresponding plugin state transition.
55 /** @enum {number} */
56 remoting.ClientSession.State = {
57 CREATED: -3,
58 BAD_PLUGIN_VERSION: -2,
59 UNKNOWN_PLUGIN_ERROR: -1,
60 UNKNOWN: 0,
61 CONNECTING: 1,
62 INITIALIZING: 2,
63 CONNECTED: 3,
64 CLOSED: 4,
65 CONNECTION_FAILED: 5
66 };
67
68 /** @enum {number} */
69 remoting.ClientSession.ConnectionError = {
70 NONE: 0,
71 HOST_IS_OFFLINE: 1,
72 SESSION_REJECTED: 2,
73 INCOMPATIBLE_PROTOCOL: 3,
74 NETWORK_FAILURE: 4
75 };
76
77 // Keys for connection statistics.
78 remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH = 'video_bandwidth';
79 remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE = 'video_frame_rate';
80 remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY = 'capture_latency';
81 remoting.ClientSession.STATS_KEY_ENCODE_LATENCY = 'encode_latency';
82 remoting.ClientSession.STATS_KEY_DECODE_LATENCY = 'decode_latency';
83 remoting.ClientSession.STATS_KEY_RENDER_LATENCY = 'render_latency';
84 remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY = 'roundtrip_latency';
85
86 /**
87 * The current state of the session.
88 * @type {remoting.ClientSession.State}
89 */
90 remoting.ClientSession.prototype.state = remoting.ClientSession.State.UNKNOWN;
91
92 /**
93 * The last connection error. Set when state is set to CONNECTION_FAILED.
94 * @type {remoting.ClientSession.ConnectionError}
95 */
96 remoting.ClientSession.prototype.error =
97 remoting.ClientSession.ConnectionError.NONE;
98
99 /**
100 * Chromoting session API version (for this javascript).
101 * This is compared with the plugin API version to verify that they are
102 * compatible.
103 *
104 * @const
105 * @private
106 */
107 remoting.ClientSession.prototype.API_VERSION_ = 2;
108
109 /**
110 * The oldest API version that we support.
111 * This will differ from the |API_VERSION_| if we maintain backward
112 * compatibility with older API versions.
113 *
114 * @const
115 * @private
116 */
117 remoting.ClientSession.prototype.API_MIN_VERSION_ = 1;
118
119 /**
120 * Server used to bridge into the Jabber network for establishing Jingle
121 * connections.
122 *
123 * @const
124 * @private
125 */
126 remoting.ClientSession.prototype.HTTP_XMPP_PROXY_ =
127 'https://chromoting-httpxmpp-oauth2-dev.corp.google.com';
128
129 /**
130 * The id of the client plugin
131 *
132 * @const
133 */
134 remoting.ClientSession.prototype.PLUGIN_ID = 'session-client-plugin';
135
136 /**
137 * Callback to invoke when the state is changed.
138 *
139 * @param {remoting.ClientSession.State} oldState The previous state.
140 * @param {remoting.ClientSession.State} newState The current state.
141 */
142 remoting.ClientSession.prototype.onStateChange =
143 function(oldState, newState) { };
144
145 /**
146 * Adds <embed> element to |container| and readies the sesion object.
147 *
148 * @param {Element} container The element to add the plugin to.
149 * @param {string} oauth2AccessToken A valid OAuth2 access token.
150 * @return {void} Nothing.
151 */
152 remoting.ClientSession.prototype.createPluginAndConnect =
153 function(container, oauth2AccessToken) {
154 this.plugin = /** @type {remoting.ViewerPlugin} */
155 document.createElement('embed');
156 this.plugin.id = this.PLUGIN_ID;
157 this.plugin.src = 'about://none';
158 this.plugin.type = 'pepper-application/x-chromoting';
159 this.plugin.width = 0;
160 this.plugin.height = 0;
161 this.plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
162 container.appendChild(this.plugin);
163
164 this.plugin.focus();
165 this.plugin.addEventListener('blur', this.refocusPlugin_, false);
166
167 if (!this.isPluginVersionSupported_(this.plugin)) {
168 // TODO(ajwong): Remove from parent.
169 delete this.plugin;
170 this.setState_(remoting.ClientSession.State.BAD_PLUGIN_VERSION);
171 return;
172 }
173
174 /** @type {remoting.ClientSession} */ var that = this;
175 /** @param {string} msg The IQ stanza to send. */
176 this.plugin.sendIq = function(msg) { that.sendIq_(msg); };
177 /** @param {string} msg The message to log. */
178 this.plugin.debugInfo = function(msg) {
179 remoting.debug.log('plugin: ' + msg);
180 };
181
182 // TODO(ajwong): Is it even worth having this class handle these events?
183 // Or would it be better to just allow users to pass in their own handlers
184 // and leave these blank by default?
185 /**
186 * @param {number} status The plugin status.
187 * @param {number} error The plugin error status, if any.
188 */
189 this.plugin.connectionInfoUpdate = function(status, error) {
190 that.connectionInfoUpdateCallback(status, error);
191 };
192 this.plugin.desktopSizeUpdate = function() { that.onDesktopSizeChanged_(); };
193
194 // TODO(garykac): Clean exit if |connect| isn't a function.
195 if (typeof this.plugin.connect === 'function') {
196 this.connectPluginToWcs_(oauth2AccessToken);
197 } else {
198 remoting.debug.log('ERROR: remoting plugin not loaded');
199 this.setState_(remoting.ClientSession.State.UNKNOWN_PLUGIN_ERROR);
200 }
201 };
202
203 /**
204 * Deletes the <embed> element from the container, without sending a
205 * session_terminate request. This is to be called when the session was
206 * disconnected by the Host.
207 *
208 * @return {void} Nothing.
209 */
210 remoting.ClientSession.prototype.removePlugin = function() {
211 if (this.plugin) {
212 this.plugin.removeEventListener('blur', this.refocusPlugin_, false);
213 var parentNode = this.plugin.parentNode;
214 parentNode.removeChild(this.plugin);
215 this.plugin = null;
216 }
217 };
218
219 /**
220 * Deletes the <embed> element from the container and disconnects.
221 *
222 * @return {void} Nothing.
223 */
224 remoting.ClientSession.prototype.disconnect = function() {
225 // The plugin won't send a state change notification, so we explicitly log
226 // the fact that the connection has closed.
227 this.logToServer.logClientSessionStateChange(
228 remoting.ClientSession.State.CLOSED,
229 remoting.ClientSession.ConnectionError.NONE);
230 if (remoting.wcs) {
231 remoting.wcs.setOnIq(function(stanza) {});
232 this.sendIq_(
233 '<cli:iq ' +
234 'to="' + this.hostJid + '" ' +
235 'type="set" ' +
236 'id="session-terminate" ' +
237 'xmlns:cli="jabber:client">' +
238 '<jingle ' +
239 'xmlns="urn:xmpp:jingle:1" ' +
240 'action="session-terminate" ' +
241 'initiator="' + this.clientJid + '" ' +
242 'sid="' + this.sessionId + '">' +
243 '<reason><success/></reason>' +
244 '</jingle>' +
245 '</cli:iq>');
246 }
247 this.removePlugin();
248 };
249
250 /**
251 * Sends an IQ stanza via the http xmpp proxy.
252 *
253 * @private
254 * @param {string} msg XML string of IQ stanza to send to server.
255 * @return {void} Nothing.
256 */
257 remoting.ClientSession.prototype.sendIq_ = function(msg) {
258 remoting.debug.logIq(true, msg);
259 // Extract the session id, so we can close the session later.
260 var parser = new DOMParser();
261 var iqNode = parser.parseFromString(msg, 'text/xml').firstChild;
262 var jingleNode = iqNode.firstChild;
263 if (jingleNode) {
264 var action = jingleNode.getAttribute('action');
265 if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') {
266 this.sessionId = jingleNode.getAttribute('sid');
267 }
268 }
269
270 // Send the stanza.
271 if (remoting.wcs) {
272 remoting.wcs.sendIq(msg);
273 } else {
274 remoting.debug.log('Tried to send IQ before WCS was ready.');
275 this.setState_(remoting.ClientSession.State.CONNECTION_FAILED);
276 }
277 };
278
279 /**
280 * @private
281 * @param {remoting.ViewerPlugin} plugin The embed element for the plugin.
282 * @return {boolean} True if the plugin and web-app versions are compatible.
283 */
284 remoting.ClientSession.prototype.isPluginVersionSupported_ = function(plugin) {
285 return this.API_VERSION_ >= plugin.apiMinVersion &&
286 plugin.apiVersion >= this.API_MIN_VERSION_;
287 };
288
289 /**
290 * Connects the plugin to WCS.
291 *
292 * @private
293 * @param {string} oauth2AccessToken A valid OAuth2 access token.
294 * @return {void} Nothing.
295 */
296 remoting.ClientSession.prototype.connectPluginToWcs_ =
297 function(oauth2AccessToken) {
298 this.clientJid = remoting.wcs.getJid();
299 if (this.clientJid == '') {
300 remoting.debug.log('Tried to connect without a full JID.');
301 }
302 remoting.debug.setJids(this.clientJid, this.hostJid);
303 /** @type {remoting.ClientSession} */
304 var that = this;
305 /** @param {string} stanza The IQ stanza received. */
306 var onIq = function(stanza) {
307 remoting.debug.logIq(false, stanza);
308 if (that.plugin.onIq) {
309 that.plugin.onIq(stanza);
310 } else {
311 // plugin.onIq may not be set after the plugin has been shut
312 // down. Particularly this happens when we receive response to
313 // session-terminate stanza.
314 remoting.debug.log(
315 'plugin.onIq is not set so dropping incoming message.');
316 }
317 }
318 remoting.wcs.setOnIq(onIq);
319 that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid,
320 this.authenticationCode);
321 };
322
323 /**
324 * Callback that the plugin invokes to indicate that the connection
325 * status has changed.
326 *
327 * @param {number} status The plugin's status.
328 * @param {number} error The plugin's error state, if any.
329 */
330 remoting.ClientSession.prototype.connectionInfoUpdateCallback =
331 function(status, error) {
332 // Old plugins didn't pass the status and error values, so get them directly.
333 // Note that there is a race condition inherent in this approach.
334 if (typeof(status) == 'undefined') {
335 status = this.plugin.status;
336 }
337 if (typeof(error) == 'undefined') {
338 error = this.plugin.error;
339 }
340
341 if (status == this.plugin.STATUS_CONNECTED) {
342 this.onDesktopSizeChanged_();
343 } else if (status == this.plugin.STATUS_FAILED) {
344 this.error = /** @type {remoting.ClientSession.ConnectionError} */ (error);
345 }
346 this.setState_(/** @type {remoting.ClientSession.State} */ (status));
347 };
348
349 /**
350 * @private
351 * @param {remoting.ClientSession.State} newState The new state for the session.
352 * @return {void} Nothing.
353 */
354 remoting.ClientSession.prototype.setState_ = function(newState) {
355 var oldState = this.state;
356 this.state = newState;
357 if (this.onStateChange) {
358 this.onStateChange(oldState, newState);
359 }
360 this.logToServer.logClientSessionStateChange(this.state, this.error);
361 };
362
363 /**
364 * This is a callback that gets called when the window is resized.
365 *
366 * @return {void} Nothing.
367 */
368 remoting.ClientSession.prototype.onResize = function() {
369 this.updateDimensions();
370 };
371
372 /**
373 * This is a callback that gets called when the plugin notifies us of a change
374 * in the size of the remote desktop.
375 *
376 * @private
377 * @return {void} Nothing.
378 */
379 remoting.ClientSession.prototype.onDesktopSizeChanged_ = function() {
380 remoting.debug.log('desktop size changed: ' +
381 this.plugin.desktopWidth + 'x' +
382 this.plugin.desktopHeight);
383 this.updateDimensions();
384 };
385
386 /**
387 * Refreshes the plugin's dimensions, taking into account the sizes of the
388 * remote desktop and client window, and the current scale-to-fit setting.
389 *
390 * @return {void} Nothing.
391 */
392 remoting.ClientSession.prototype.updateDimensions = function() {
393 if (this.plugin.desktopWidth == 0 ||
394 this.plugin.desktopHeight == 0)
395 return;
396
397 var windowWidth = window.innerWidth;
398 var windowHeight = window.innerHeight;
399 var scale = 1.0;
400
401 if (remoting.scaleToFit) {
402 var scaleFitHeight = 1.0 * windowHeight / this.plugin.desktopHeight;
403 var scaleFitWidth = 1.0 * windowWidth / this.plugin.desktopWidth;
404 scale = Math.min(1.0, scaleFitHeight, scaleFitWidth);
405 }
406
407 // Resize the plugin if necessary.
408 this.plugin.width = this.plugin.desktopWidth * scale;
409 this.plugin.height = this.plugin.desktopHeight * scale;
410
411 // Position the container.
412 // TODO(wez): We should take into account scrollbars when positioning.
413 var parentNode = this.plugin.parentNode;
414 if (this.plugin.width < windowWidth)
415 parentNode.style.left = (windowWidth - this.plugin.width) / 2 + 'px';
416 else
417 parentNode.style.left = '0';
418 if (this.plugin.height < windowHeight)
419 parentNode.style.top = (windowHeight - this.plugin.height) / 2 + 'px';
420 else
421 parentNode.style.top = '0';
422
423 remoting.debug.log('plugin dimensions: ' +
424 parentNode.style.left + ',' +
425 parentNode.style.top + '-' +
426 this.plugin.width + 'x' + this.plugin.height + '.');
427 this.plugin.setScaleToFit(remoting.scaleToFit);
428 };
429
430 /**
431 * Returns an associative array with a set of stats for this connection.
432 *
433 * @return {Object.<string, number>} The connection statistics.
434 */
435 remoting.ClientSession.prototype.stats = function() {
436 var dict = {};
437 dict[remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH] =
438 this.plugin.videoBandwidth;
439 dict[remoting.ClientSession.STATS_KEY_VIDEO_FRAME_RATE] =
440 this.plugin.videoFrameRate;
441 dict[remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY] =
442 this.plugin.videoCaptureLatency;
443 dict[remoting.ClientSession.STATS_KEY_ENCODE_LATENCY] =
444 this.plugin.videoEncodeLatency;
445 dict[remoting.ClientSession.STATS_KEY_DECODE_LATENCY] =
446 this.plugin.videoDecodeLatency;
447 dict[remoting.ClientSession.STATS_KEY_RENDER_LATENCY] =
448 this.plugin.videoRenderLatency;
449 dict[remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY] =
450 this.plugin.roundTripLatency;
451 return dict;
452 };
453
454 /**
455 * Logs statistics.
456 *
457 * @param {Object.<string, number>} stats
458 */
459 remoting.ClientSession.prototype.logStatistics = function(stats) {
460 this.logToServer.logStatistics(stats);
461 };
OLDNEW
« no previous file with comments | « remoting/webapp/me2mom/client_screen.js ('k') | remoting/webapp/me2mom/cs_oauth2_trampoline.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698