| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview | 6 * @fileoverview |
| 7 * Class handling creation and teardown of a remoting client session. | 7 * Class handling creation and teardown of a remoting client session. |
| 8 * | 8 * |
| 9 * The ClientSession class controls lifetime of the client plugin | 9 * The ClientSession class controls lifetime of the client plugin |
| 10 * object and provides the plugin with the functionality it needs to | 10 * object and provides the plugin with the functionality it needs to |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 var remoting = remoting || {}; | 23 var remoting = remoting || {}; |
| 24 | 24 |
| 25 /** | 25 /** |
| 26 * True if Cast capability is supported. | 26 * True if Cast capability is supported. |
| 27 * | 27 * |
| 28 * @type {boolean} | 28 * @type {boolean} |
| 29 */ | 29 */ |
| 30 remoting.enableCast = false; | 30 remoting.enableCast = false; |
| 31 | 31 |
| 32 /** | 32 /** |
| 33 * @param {remoting.SignalStrategy} signalStrategy Signal strategy. |
| 33 * @param {HTMLElement} container Container element for the client view. | 34 * @param {HTMLElement} container Container element for the client view. |
| 34 * @param {string} hostDisplayName A human-readable name for the host. | 35 * @param {string} hostDisplayName A human-readable name for the host. |
| 35 * @param {string} accessCode The IT2Me access code. Blank for Me2Me. | 36 * @param {string} accessCode The IT2Me access code. Blank for Me2Me. |
| 36 * @param {function(boolean, function(string): void): void} fetchPin | 37 * @param {function(boolean, function(string): void): void} fetchPin |
| 37 * Called by Me2Me connections when a PIN needs to be obtained | 38 * Called by Me2Me connections when a PIN needs to be obtained |
| 38 * interactively. | 39 * interactively. |
| 39 * @param {function(string, string, string, | 40 * @param {function(string, string, string, |
| 40 * function(string, string): void): void} | 41 * function(string, string): void): void} |
| 41 * fetchThirdPartyToken Called by Me2Me connections when a third party | 42 * fetchThirdPartyToken Called by Me2Me connections when a third party |
| 42 * authentication token must be obtained. | 43 * authentication token must be obtained. |
| 43 * @param {string} authenticationMethods Comma-separated list of | 44 * @param {string} authenticationMethods Comma-separated list of |
| 44 * authentication methods the client should attempt to use. | 45 * authentication methods the client should attempt to use. |
| 45 * @param {string} hostId The host identifier for Me2Me, or empty for IT2Me. | 46 * @param {string} hostId The host identifier for Me2Me, or empty for IT2Me. |
| 46 * Mixed into authentication hashes for some authentication methods. | 47 * Mixed into authentication hashes for some authentication methods. |
| 47 * @param {string} hostJid The jid of the host to connect to. | 48 * @param {string} hostJid The jid of the host to connect to. |
| 48 * @param {string} hostPublicKey The base64 encoded version of the host's | 49 * @param {string} hostPublicKey The base64 encoded version of the host's |
| 49 * public key. | 50 * public key. |
| 50 * @param {remoting.ClientSession.Mode} mode The mode of this connection. | 51 * @param {remoting.ClientSession.Mode} mode The mode of this connection. |
| 51 * @param {string} clientPairingId For paired Me2Me connections, the | 52 * @param {string} clientPairingId For paired Me2Me connections, the |
| 52 * pairing id for this client, as issued by the host. | 53 * pairing id for this client, as issued by the host. |
| 53 * @param {string} clientPairedSecret For paired Me2Me connections, the | 54 * @param {string} clientPairedSecret For paired Me2Me connections, the |
| 54 * paired secret for this client, as issued by the host. | 55 * paired secret for this client, as issued by the host. |
| 55 * @constructor | 56 * @constructor |
| 56 * @extends {base.EventSource} | 57 * @extends {base.EventSource} |
| 57 */ | 58 */ |
| 58 remoting.ClientSession = function(container, hostDisplayName, accessCode, | 59 remoting.ClientSession = function(signalStrategy, container, hostDisplayName, |
| 59 fetchPin, fetchThirdPartyToken, | 60 accessCode, fetchPin, fetchThirdPartyToken, |
| 60 authenticationMethods, hostId, hostJid, | 61 authenticationMethods, hostId, hostJid, |
| 61 hostPublicKey, mode, clientPairingId, | 62 hostPublicKey, mode, clientPairingId, |
| 62 clientPairedSecret) { | 63 clientPairedSecret) { |
| 63 /** @private */ | 64 /** @private */ |
| 64 this.state_ = remoting.ClientSession.State.CREATED; | 65 this.state_ = remoting.ClientSession.State.CREATED; |
| 65 | 66 |
| 66 /** @private */ | 67 /** @private */ |
| 67 this.error_ = remoting.Error.NONE; | 68 this.error_ = remoting.Error.NONE; |
| 68 | 69 |
| 69 /** @type {HTMLElement} | 70 /** @type {HTMLElement} |
| (...skipping 30 matching lines...) Expand all Loading... |
| 100 /** @private */ | 101 /** @private */ |
| 101 this.shrinkToFit_ = true; | 102 this.shrinkToFit_ = true; |
| 102 /** @private */ | 103 /** @private */ |
| 103 this.resizeToClient_ = true; | 104 this.resizeToClient_ = true; |
| 104 /** @private */ | 105 /** @private */ |
| 105 this.remapKeys_ = ''; | 106 this.remapKeys_ = ''; |
| 106 /** @private */ | 107 /** @private */ |
| 107 this.hasReceivedFrame_ = false; | 108 this.hasReceivedFrame_ = false; |
| 108 this.logToServer = new remoting.LogToServer(); | 109 this.logToServer = new remoting.LogToServer(); |
| 109 | 110 |
| 111 /** @private */ |
| 112 this.signalStrategy_ = signalStrategy; |
| 113 base.debug.assert(this.signalStrategy_.getState() == |
| 114 remoting.SignalStrategy.State.CONNECTED); |
| 115 this.signalStrategy_.setIncomingStanzaCallback( |
| 116 this.onIncomingMessage_.bind(this)); |
| 117 remoting.formatIq.setJids(this.signalStrategy_.getJid(), hostJid); |
| 118 |
| 110 /** @type {number?} @private */ | 119 /** @type {number?} @private */ |
| 111 this.notifyClientResolutionTimer_ = null; | 120 this.notifyClientResolutionTimer_ = null; |
| 112 /** @type {number?} @private */ | 121 /** @type {number?} @private */ |
| 113 this.bumpScrollTimer_ = null; | 122 this.bumpScrollTimer_ = null; |
| 114 | 123 |
| 115 // Bump-scroll test variables. Override to use a fake value for the width | 124 // Bump-scroll test variables. Override to use a fake value for the width |
| 116 // and height of the client plugin so that bump-scrolling can be tested | 125 // and height of the client plugin so that bump-scrolling can be tested |
| 117 // without relying on the actual size of the host desktop. | 126 // without relying on the actual size of the host desktop. |
| 118 /** @type {number} @private */ | 127 /** @type {number} @private */ |
| 119 this.pluginWidthForBumpScrollTesting = 0; | 128 this.pluginWidthForBumpScrollTesting = 0; |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 483 'focus', this.callPluginGotFocus_, false); | 492 'focus', this.callPluginGotFocus_, false); |
| 484 this.plugin_.element().addEventListener( | 493 this.plugin_.element().addEventListener( |
| 485 'blur', this.callPluginLostFocus_, false); | 494 'blur', this.callPluginLostFocus_, false); |
| 486 this.plugin_.element().focus(); | 495 this.plugin_.element().focus(); |
| 487 }; | 496 }; |
| 488 | 497 |
| 489 /** | 498 /** |
| 490 * @param {remoting.Error} error | 499 * @param {remoting.Error} error |
| 491 */ | 500 */ |
| 492 remoting.ClientSession.prototype.resetWithError_ = function(error) { | 501 remoting.ClientSession.prototype.resetWithError_ = function(error) { |
| 502 this.signalStrategy_.setIncomingStanzaCallback(null); |
| 493 this.plugin_.cleanup(); | 503 this.plugin_.cleanup(); |
| 494 this.plugin_ = null; | 504 this.plugin_ = null; |
| 495 this.error_ = error; | 505 this.error_ = error; |
| 496 this.setState_(remoting.ClientSession.State.FAILED); | 506 this.setState_(remoting.ClientSession.State.FAILED); |
| 497 } | 507 } |
| 498 | 508 |
| 499 /** | 509 /** |
| 500 * @param {boolean} initialized | 510 * @param {boolean} initialized |
| 501 */ | 511 */ |
| 502 remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) { | 512 remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 520 } else if (this.mode_ != remoting.ClientSession.Mode.ME2ME) { | 530 } else if (this.mode_ != remoting.ClientSession.Mode.ME2ME) { |
| 521 var sendCadElement = document.getElementById('send-ctrl-alt-del'); | 531 var sendCadElement = document.getElementById('send-ctrl-alt-del'); |
| 522 sendCadElement.hidden = true; | 532 sendCadElement.hidden = true; |
| 523 } | 533 } |
| 524 | 534 |
| 525 // Apply customized key remappings if the plugin supports remapKeys. | 535 // Apply customized key remappings if the plugin supports remapKeys. |
| 526 if (this.plugin_.hasFeature(remoting.ClientPlugin.Feature.REMAP_KEY)) { | 536 if (this.plugin_.hasFeature(remoting.ClientPlugin.Feature.REMAP_KEY)) { |
| 527 this.applyRemapKeys_(true); | 537 this.applyRemapKeys_(true); |
| 528 } | 538 } |
| 529 | 539 |
| 530 | |
| 531 // Enable MediaSource-based rendering on Chrome 37 and above. | 540 // Enable MediaSource-based rendering on Chrome 37 and above. |
| 532 var chromeVersionMajor = | 541 var chromeVersionMajor = |
| 533 parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10); | 542 parseInt((remoting.getChromeVersion() || '0').split('.')[0], 10); |
| 534 if (chromeVersionMajor >= 37 && | 543 if (chromeVersionMajor >= 37 && |
| 535 this.plugin_.hasFeature( | 544 this.plugin_.hasFeature( |
| 536 remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) { | 545 remoting.ClientPlugin.Feature.MEDIA_SOURCE_RENDERING)) { |
| 537 this.video_ = /** @type {HTMLMediaElement} */( | 546 this.video_ = /** @type {HTMLMediaElement} */( |
| 538 this.container_.querySelector('video')); | 547 this.container_.querySelector('video')); |
| 539 // Make sure that the <video> element is hidden until we get the first | 548 // Make sure that the <video> element is hidden until we get the first |
| 540 // frame. | 549 // frame. |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 632 this.error_ = error; | 641 this.error_ = error; |
| 633 this.setState_(state); | 642 this.setState_(state); |
| 634 }; | 643 }; |
| 635 | 644 |
| 636 /** | 645 /** |
| 637 * Deletes the <embed> element from the container and disconnects. | 646 * Deletes the <embed> element from the container and disconnects. |
| 638 * | 647 * |
| 639 * @return {void} Nothing. | 648 * @return {void} Nothing. |
| 640 */ | 649 */ |
| 641 remoting.ClientSession.prototype.cleanup = function() { | 650 remoting.ClientSession.prototype.cleanup = function() { |
| 642 remoting.wcsSandbox.setOnIq(null); | |
| 643 this.sendIq_( | 651 this.sendIq_( |
| 644 '<cli:iq ' + | 652 '<cli:iq ' + |
| 645 'to="' + this.hostJid_ + '" ' + | 653 'to="' + this.hostJid_ + '" ' + |
| 646 'type="set" ' + | 654 'type="set" ' + |
| 647 'id="session-terminate" ' + | 655 'id="session-terminate" ' + |
| 648 'xmlns:cli="jabber:client">' + | 656 'xmlns:cli="jabber:client">' + |
| 649 '<jingle ' + | 657 '<jingle ' + |
| 650 'xmlns="urn:xmpp:jingle:1" ' + | 658 'xmlns="urn:xmpp:jingle:1" ' + |
| 651 'action="session-terminate" ' + | 659 'action="session-terminate" ' + |
| 652 'sid="' + this.sessionId_ + '">' + | 660 'sid="' + this.sessionId_ + '">' + |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 825 }; | 833 }; |
| 826 | 834 |
| 827 /** | 835 /** |
| 828 * @return {boolean} Whether the client has received a video buffer. | 836 * @return {boolean} Whether the client has received a video buffer. |
| 829 */ | 837 */ |
| 830 remoting.ClientSession.prototype.hasReceivedFrame = function() { | 838 remoting.ClientSession.prototype.hasReceivedFrame = function() { |
| 831 return this.hasReceivedFrame_; | 839 return this.hasReceivedFrame_; |
| 832 }; | 840 }; |
| 833 | 841 |
| 834 /** | 842 /** |
| 835 * Sends an IQ stanza via the http xmpp proxy. | 843 * Sends a signaling message. |
| 836 * | 844 * |
| 837 * @private | 845 * @private |
| 838 * @param {string} msg XML string of IQ stanza to send to server. | 846 * @param {string} message XML string of IQ stanza to send to server. |
| 839 * @return {void} Nothing. | 847 * @return {void} Nothing. |
| 840 */ | 848 */ |
| 841 remoting.ClientSession.prototype.sendIq_ = function(msg) { | 849 remoting.ClientSession.prototype.sendIq_ = function(message) { |
| 842 // Extract the session id, so we can close the session later. | 850 // Extract the session id, so we can close the session later. |
| 843 var parser = new DOMParser(); | 851 var parser = new DOMParser(); |
| 844 var iqNode = parser.parseFromString(msg, 'text/xml').firstChild; | 852 var iqNode = parser.parseFromString(message, 'text/xml').firstChild; |
| 845 var jingleNode = iqNode.firstChild; | 853 var jingleNode = iqNode.firstChild; |
| 846 if (jingleNode) { | 854 if (jingleNode) { |
| 847 var action = jingleNode.getAttribute('action'); | 855 var action = jingleNode.getAttribute('action'); |
| 848 if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') { | 856 if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') { |
| 849 this.sessionId_ = jingleNode.getAttribute('sid'); | 857 this.sessionId_ = jingleNode.getAttribute('sid'); |
| 850 } | 858 } |
| 851 } | 859 } |
| 852 | 860 |
| 853 // HACK: Add 'x' prefix to the IDs of the outgoing messages to make sure that | 861 console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(message)); |
| 854 // stanza IDs used by host and client do not match. This is necessary to | 862 if (this.signalStrategy_.getState() != |
| 855 // workaround bug in the signaling endpoint used by chromoting. | 863 remoting.SignalStrategy.State.CONNECTED) { |
| 856 // TODO(sergeyu): Remove this hack once the server-side bug is fixed. | 864 console.log("Message above is dropped because signaling is not connected."); |
| 857 var type = iqNode.getAttribute('type'); | 865 return; |
| 858 if (type == 'set') { | |
| 859 var id = iqNode.getAttribute('id'); | |
| 860 iqNode.setAttribute('id', 'x' + id); | |
| 861 msg = (new XMLSerializer()).serializeToString(iqNode); | |
| 862 } | 866 } |
| 863 | 867 |
| 864 console.log(remoting.timestamp(), remoting.formatIq.prettifySendIq(msg)); | 868 this.signalStrategy_.sendMessage(message); |
| 865 | |
| 866 // Send the stanza. | |
| 867 remoting.wcsSandbox.sendIq(msg); | |
| 868 }; | 869 }; |
| 869 | 870 |
| 871 /** |
| 872 * @private |
| 873 * @param {Element} message |
| 874 */ |
| 875 remoting.ClientSession.prototype.onIncomingMessage_ = function(message) { |
| 876 if (!this.plugin_) { |
| 877 return; |
| 878 } |
| 879 var formatted = new XMLSerializer().serializeToString(message); |
| 880 console.log(remoting.timestamp(), |
| 881 remoting.formatIq.prettifyReceiveIq(formatted)); |
| 882 this.plugin_.onIncomingIq(formatted); |
| 883 } |
| 884 |
| 885 /** |
| 886 * @private |
| 887 */ |
| 870 remoting.ClientSession.prototype.initiateConnection_ = function() { | 888 remoting.ClientSession.prototype.initiateConnection_ = function() { |
| 871 /** @type {remoting.ClientSession} */ | 889 /** @type {remoting.ClientSession} */ |
| 872 var that = this; | 890 var that = this; |
| 873 | 891 |
| 874 remoting.wcsSandbox.connect(onWcsConnected, this.resetWithError_.bind(this)); | 892 /** @param {string} sharedSecret Shared secret. */ |
| 893 function onSharedSecretReceived(sharedSecret) { |
| 894 that.plugin_.connect( |
| 895 that.hostJid_, that.hostPublicKey_, that.signalStrategy_.getJid(), |
| 896 sharedSecret, that.authenticationMethods_, that.hostId_, |
| 897 that.clientPairingId_, that.clientPairedSecret_); |
| 898 }; |
| 875 | 899 |
| 876 /** @param {string} localJid Local JID. */ | 900 this.getSharedSecret_(onSharedSecretReceived); |
| 877 function onWcsConnected(localJid) { | |
| 878 that.connectPluginToWcs_(localJid); | |
| 879 that.getSharedSecret_(onSharedSecretReceived.bind(null, localJid)); | |
| 880 } | |
| 881 | |
| 882 /** @param {string} localJid Local JID. | |
| 883 * @param {string} sharedSecret Shared secret. */ | |
| 884 function onSharedSecretReceived(localJid, sharedSecret) { | |
| 885 that.plugin_.connect( | |
| 886 that.hostJid_, that.hostPublicKey_, localJid, sharedSecret, | |
| 887 that.authenticationMethods_, that.hostId_, that.clientPairingId_, | |
| 888 that.clientPairedSecret_); | |
| 889 }; | |
| 890 } | 901 } |
| 891 | 902 |
| 892 /** | 903 /** |
| 893 * Connects the plugin to WCS. | |
| 894 * | |
| 895 * @private | |
| 896 * @param {string} localJid Local JID. | |
| 897 * @return {void} Nothing. | |
| 898 */ | |
| 899 remoting.ClientSession.prototype.connectPluginToWcs_ = function(localJid) { | |
| 900 remoting.formatIq.setJids(localJid, this.hostJid_); | |
| 901 var forwardIq = this.plugin_.onIncomingIq.bind(this.plugin_); | |
| 902 /** @param {string} stanza The IQ stanza received. */ | |
| 903 var onIncomingIq = function(stanza) { | |
| 904 // HACK: Remove 'x' prefix added to the id in sendIq_(). | |
| 905 try { | |
| 906 var parser = new DOMParser(); | |
| 907 var iqNode = parser.parseFromString(stanza, 'text/xml').firstChild; | |
| 908 var type = iqNode.getAttribute('type'); | |
| 909 var id = iqNode.getAttribute('id'); | |
| 910 if (type != 'set' && id.charAt(0) == 'x') { | |
| 911 iqNode.setAttribute('id', id.substr(1)); | |
| 912 stanza = (new XMLSerializer()).serializeToString(iqNode); | |
| 913 } | |
| 914 } catch (err) { | |
| 915 // Pass message as is when it is malformed. | |
| 916 } | |
| 917 | |
| 918 console.log(remoting.timestamp(), | |
| 919 remoting.formatIq.prettifyReceiveIq(stanza)); | |
| 920 forwardIq(stanza); | |
| 921 }; | |
| 922 remoting.wcsSandbox.setOnIq(onIncomingIq); | |
| 923 } | |
| 924 | |
| 925 /** | |
| 926 * Gets shared secret to be used for connection. | 904 * Gets shared secret to be used for connection. |
| 927 * | 905 * |
| 928 * @param {function(string)} callback Callback called with the shared secret. | 906 * @param {function(string)} callback Callback called with the shared secret. |
| 929 * @return {void} Nothing. | 907 * @return {void} Nothing. |
| 930 * @private | 908 * @private |
| 931 */ | 909 */ |
| 932 remoting.ClientSession.prototype.getSharedSecret_ = function(callback) { | 910 remoting.ClientSession.prototype.getSharedSecret_ = function(callback) { |
| 933 /** @type remoting.ClientSession */ | 911 /** @type remoting.ClientSession */ |
| 934 var that = this; | 912 var that = this; |
| 935 if (this.plugin_.hasFeature(remoting.ClientPlugin.Feature.THIRD_PARTY_AUTH)) { | 913 if (this.plugin_.hasFeature(remoting.ClientPlugin.Feature.THIRD_PARTY_AUTH)) { |
| (...skipping 699 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1635 * @param {string} data Contents of the extension message. | 1613 * @param {string} data Contents of the extension message. |
| 1636 * @return {boolean} True if the message was recognized, false otherwise. | 1614 * @return {boolean} True if the message was recognized, false otherwise. |
| 1637 */ | 1615 */ |
| 1638 remoting.ClientSession.prototype.handleExtensionMessage = | 1616 remoting.ClientSession.prototype.handleExtensionMessage = |
| 1639 function(type, data) { | 1617 function(type, data) { |
| 1640 if (this.videoFrameRecorder_) { | 1618 if (this.videoFrameRecorder_) { |
| 1641 return this.videoFrameRecorder_.handleMessage(type, data); | 1619 return this.videoFrameRecorder_.handleMessage(type, data); |
| 1642 } | 1620 } |
| 1643 return false; | 1621 return false; |
| 1644 } | 1622 } |
| OLD | NEW |