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 |