diff options
author | simonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-16 17:28:17 +0000 |
---|---|---|
committer | simonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-16 17:28:17 +0000 |
commit | aa69f38cf90f4ee19d0338ab37bdc2e452d364ba (patch) | |
tree | 54c63a7f3bc835e273809327e155ef5df122e53b | |
parent | 089afea7c5ad19900c302314d8ee67df1acd7106 (diff) | |
download | chromium_src-aa69f38cf90f4ee19d0338ab37bdc2e452d364ba.zip chromium_src-aa69f38cf90f4ee19d0338ab37bdc2e452d364ba.tar.gz chromium_src-aa69f38cf90f4ee19d0338ab37bdc2e452d364ba.tar.bz2 |
The webapp sends and receives IQ stanzas over WCS.
R=jamiewalch@chromium.org
BUG=91606
TEST=none
Review URL: http://codereview.chromium.org/7787016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101505 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | remoting/remoting.gyp | 2 | ||||
-rw-r--r-- | remoting/webapp/me2mom/choice.html | 2 | ||||
-rw-r--r-- | remoting/webapp/me2mom/client_session.js | 140 | ||||
-rw-r--r-- | remoting/webapp/me2mom/oauth2.js | 14 | ||||
-rw-r--r-- | remoting/webapp/me2mom/remoting.js | 46 | ||||
-rwxr-xr-x | remoting/webapp/me2mom/wcs.js | 144 | ||||
-rw-r--r-- | remoting/webapp/me2mom/wcs_iq_client_proto.js | 29 | ||||
-rwxr-xr-x | remoting/webapp/me2mom/wcs_loader.js | 139 |
8 files changed, 396 insertions, 120 deletions
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index b4b73dc..bf467b4 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -113,6 +113,8 @@ 'webapp/me2mom/scale-to-fit.png', 'webapp/me2mom/spinner.gif', 'webapp/me2mom/toolbar.css', + 'webapp/me2mom/wcs.js', + 'webapp/me2mom/wcs_loader.js', 'webapp/me2mom/xhr.js', 'resources/chromoting128.png', 'resources/chromoting16.png', diff --git a/remoting/webapp/me2mom/choice.html b/remoting/webapp/me2mom/choice.html index db0b162..ae7a1d4 100644 --- a/remoting/webapp/me2mom/choice.html +++ b/remoting/webapp/me2mom/choice.html @@ -22,6 +22,8 @@ found in the LICENSE file. <script src="plugin_settings.js"></script> <script src="remoting.js"></script> <script src="xhr.js"></script> + <script src="wcs.js"></script> + <script src="wcs_loader.js"></script> <title i18n-content="PRODUCT_NAME"></title> </head> diff --git a/remoting/webapp/me2mom/client_session.js b/remoting/webapp/me2mom/client_session.js index 7cbbb0d..4940ed8 100644 --- a/remoting/webapp/me2mom/client_session.js +++ b/remoting/webapp/me2mom/client_session.js @@ -148,7 +148,7 @@ remoting.ClientSession.prototype.createPluginAndConnect = // TODO(garykac): Clean exit if |connect| isn't a function. if (typeof this.plugin.connect === 'function') { - this.registerConnection_(oauth2AccessToken); + this.connectPluginToWcs_(oauth2AccessToken); } else { remoting.debug.log('ERROR: remoting plugin not loaded'); this.setState_(remoting.ClientSession.State.UNKNOWN_PLUGIN_ERROR); @@ -177,21 +177,24 @@ remoting.ClientSession.prototype.removePlugin = function() { * @return {void} Nothing. */ remoting.ClientSession.prototype.disconnect = function() { + if (remoting.wcs) { + remoting.wcs.setOnIq(function(stanza) {}); + } this.removePlugin(); - var parameters = { - 'to': this.hostJid, - 'payload_xml': - '<jingle xmlns="urn:xmpp:jingle:1"' + - ' action="session-terminate"' + - ' initiator="' + this.clientJid + '"' + - ' sid="' + this.sessionId + '">' + + this.sendIq_( + '<cli:iq ' + + 'to="' + this.hostJid + '" ' + + 'type="set" ' + + 'id="session-terminate" ' + + 'xmlns:cli="jabber:client">' + + '<jingle ' + + 'xmlns="urn:xmpp:jingle:1" ' + + 'action="session-terminate" ' + + 'initiator="' + this.clientJid + '" ' + + 'sid="' + this.sessionId + '">' + '<reason><success/></reason>' + - '</jingle>', - 'id': 'session_terminate', - 'type': 'set', - 'host_jid': this.hostJid - }; - this.sendIqWithParameters_(parameters); + '</jingle>' + + '</cli:iq>'); }; /** @@ -203,70 +206,24 @@ remoting.ClientSession.prototype.disconnect = function() { */ remoting.ClientSession.prototype.sendIq_ = function(msg) { remoting.debug.log('Sending Iq: ' + msg); - - // Extract the top level fields of the Iq packet. - // TODO(ajwong): Can the plugin just return these fields broken out. + // Extract the session id, so we can close the session later. var parser = new DOMParser(); var iqNode = parser.parseFromString(msg, 'text/xml').firstChild; var jingleNode = iqNode.firstChild; - var serializer = new XMLSerializer(); - var parameters = { - 'to': iqNode.getAttribute('to') || '', - 'payload_xml': serializer.serializeToString(jingleNode), - 'id': iqNode.getAttribute('id') || '1', - 'type': iqNode.getAttribute('type'), - 'host_jid': this.hostJid - }; - - this.sendIqWithParameters_(parameters); - if (jingleNode) { var action = jingleNode.getAttribute('action'); if (jingleNode.nodeName == 'jingle' && action == 'session-initiate') { - // The session id is needed in order to close the session later. this.sessionId = jingleNode.getAttribute('sid'); } } -}; - -/** - * Sends an IQ stanza via the http xmpp proxy. - * - * @private - * @param {(string|Object.<string>)} parameters Parameters to include. - * @return {void} Nothing. - */ -remoting.ClientSession.prototype.sendIqWithParameters_ = function(parameters) { - remoting.xhr.post(this.HTTP_XMPP_PROXY_ + '/sendIq', function(xhr) {}, - parameters, {}, true); -}; -/** - * Executes a poll loop on the server for more IQ packet to feed to the plugin. - * - * @private - * @return {void} Nothing. - */ -remoting.ClientSession.prototype.feedIq_ = function() { - var that = this; - - var onIq = function(xhr) { - if (xhr.status == 200) { - remoting.debug.log('Receiving Iq: --' + xhr.responseText + '--'); - that.plugin.onIq(xhr.responseText); - } - if (xhr.status == 200 || xhr.status == 204) { - // Poll again. - that.feedIq_(); - } else { - remoting.debug.log('HttpXmpp gateway returned code: ' + xhr.status); - that.plugin.disconnect(); - that.setState_(remoting.ClientSession.State.CONNECTION_FAILED); - } + // Send the stanza. + if (remoting.wcs) { + remoting.wcs.sendIq(msg); + } else { + remoting.debug.log('Tried to send IQ before WCS was ready.'); + this.setState_(remoting.ClientSession.State.CONNECTION_FAILED); } - - remoting.xhr.get(this.HTTP_XMPP_PROXY_ + '/readIq', onIq, - {'host_jid': this.hostJid}, {}, true); }; /** @@ -280,45 +237,30 @@ remoting.ClientSession.prototype.isPluginVersionSupported_ = function(plugin) { }; /** - * Registers a new connection with the HttpXmpp proxy. + * Connects the plugin to WCS. * * @private * @param {string} oauth2AccessToken A valid OAuth2 access token. * @return {void} Nothing. */ -remoting.ClientSession.prototype.registerConnection_ = +remoting.ClientSession.prototype.connectPluginToWcs_ = function(oauth2AccessToken) { - var parameters = { - 'host_jid': this.hostJid, - 'username': this.email, - 'password': oauth2AccessToken - }; - + this.clientJid = remoting.wcs.getJid(); + if (this.clientJid == '') { + remoting.debug.log('Tried to connect without a full JID.'); + } var that = this; - var onRegistered = function(xhr) { - if (xhr.status != 200) { - remoting.debug.log('FailedToConnect: --' + xhr.responseText + - '-- (status=' + xhr.status + ')'); - that.setState_(remoting.ClientSession.State.CONNECTION_FAILED); - return; - } - - remoting.debug.log('Receiving Iq: --' + xhr.responseText + '--'); - that.clientJid = xhr.responseText; - - if (remoting.useP2pApi) { - that.plugin.connect(that.hostJid, that.hostPublicKey, that.clientJid, - that.accessCode, remoting.useP2pApi); - } else { - that.plugin.connect(that.hostJid, that.hostPublicKey, that.clientJid, - that.accessCode); - } - - that.feedIq_(); - }; - - remoting.xhr.post(this.HTTP_XMPP_PROXY_ + '/newConnection', - onRegistered, parameters, {}, true); + remoting.wcs.setOnIq(function(stanza) { + remoting.debug.log('Receiving Iq: ' + stanza); + that.plugin.onIq(stanza); + }); + if (remoting.useP2pApi) { + this.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, + this.accessCode, remoting.useP2pApi); + } else { + that.plugin.connect(this.hostJid, this.hostPublicKey, this.clientJid, + this.accessCode); + } }; /** diff --git a/remoting/webapp/me2mom/oauth2.js b/remoting/webapp/me2mom/oauth2.js index 34e2b91..654221f 100644 --- a/remoting/webapp/me2mom/oauth2.js +++ b/remoting/webapp/me2mom/oauth2.js @@ -162,11 +162,15 @@ remoting.OAuth2.prototype.processTokenResponse_ = function(xhr) { this.setRefreshToken(tokens['refresh_token']); } - // Offset by 30 seconds to account for RTT issues. - // TODO(ajwong): See if this is necessary, or of the protocol already - // accounts for RTT. + // Offset by 120 seconds so that we can guarantee that the token + // we return will be valid for at least 2 minutes. + // If the access token is to be useful, this object must make some + // guarantee as to how long the token will be valid for. + // The choice of 2 minutes is arbitrary, but that length of time + // is part of the contract satisfied by callWithToken(). + // Offset by a further 30 seconds to account for RTT issues. this.setAccessToken(tokens['access_token'], - tokens['expires_in'] * 1000 + Date.now() - 30000); + (tokens['expires_in'] - (120 + 30)) * 1000 + Date.now()); } else { console.log('Failed to get tokens. Status: ' + xhr.status + ' response: ' + xhr.responseText); @@ -251,6 +255,8 @@ remoting.OAuth2.prototype.exchangeCodeForToken = function(code, onDone) { * This will refresh the access token if necessary. If the access token * cannot be refreshed, an error is thrown. * + * The access token will remain valid for at least 2 minutes. + * * @param {function({token: string, expiration: number}):void} myfunc * Function to invoke with access token. * @return {void} Nothing. diff --git a/remoting/webapp/me2mom/remoting.js b/remoting/webapp/me2mom/remoting.js index 9e4f9b8..9110d4a 100644 --- a/remoting/webapp/me2mom/remoting.js +++ b/remoting/webapp/me2mom/remoting.js @@ -571,21 +571,6 @@ function resolveSupportId(supportId) { headers); } -remoting.doTryConnect = function() { - var accessCode = document.getElementById('access-code-entry').value; - remoting.accessCode = normalizeAccessCode_(accessCode); - // At present, only 12-digit access codes are supported, of which the first - // 7 characters are the supportId. - if (remoting.accessCode.length != kAccessCodeLen) { - remoting.debug.log('Bad access code length'); - showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE); - } else { - var supportId = remoting.accessCode.substring(0, kSupportIdLen); - remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); - resolveSupportId(supportId); - } -} - remoting.tryConnect = function() { if (remoting.oauth2.needsNewAccessToken()) { remoting.oauth2.refreshAccessToken(function(xhr) { @@ -595,10 +580,37 @@ remoting.tryConnect = function() { showConnectError_(remoting.ClientError.OAUTH_FETCH_FAILED); return; } - remoting.doTryConnect(); + remoting.tryConnectWithAccessToken(); }); } else { - remoting.doTryConnect(); + remoting.tryConnectWithAccessToken(); + } +} + +remoting.tryConnectWithAccessToken = function() { + if (!remoting.wcsLoader) { + remoting.wcsLoader = new remoting.WcsLoader(); + } + remoting.wcsLoader.start( + remoting.oauth2.getAccessToken(), + function(setToken) { + remoting.oauth2.callWithToken(setToken); + }, + remoting.tryConnectWithWcs); +} + +remoting.tryConnectWithWcs = function() { + var accessCode = document.getElementById('access-code-entry').value; + remoting.accessCode = normalizeAccessCode_(accessCode); + // At present, only 12-digit access codes are supported, of which the first + // 7 characters are the supportId. + if (remoting.accessCode.length != kAccessCodeLen) { + remoting.debug.log('Bad access code length'); + showConnectError_(remoting.ClientError.INVALID_ACCESS_CODE); + } else { + var supportId = remoting.accessCode.substring(0, kSupportIdLen); + remoting.setMode(remoting.AppMode.CLIENT_CONNECTING); + resolveSupportId(supportId); } } diff --git a/remoting/webapp/me2mom/wcs.js b/remoting/webapp/me2mom/wcs.js new file mode 100755 index 0000000..ff7692e --- /dev/null +++ b/remoting/webapp/me2mom/wcs.js @@ -0,0 +1,144 @@ +/* Copyright (c) 2011 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * @fileoverview + * A class that provides an interface to a WCS connection. + */ + +'use strict'; + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function() { +/** + * @constructor + * + * @param {remoting.WcsIqClient} wcsIqClient The WCS client. + * @param {string} token An OAuth2 access token. + * @param {function(): void} onReady A function called when the WCS client has + * received a full JID. + * @param {function(function(string): void): void} refreshToken A function + * called when this object wants to see whether an updated access token is + * available. The passed function will be called asynchronously with a + * (possibly updated) access token. + */ +remoting.Wcs = function(wcsIqClient, token, onReady, refreshToken) { + /** + * The WCS client. + * @type {remoting.WcsIqClient} + * @private + */ + this.wcsIqClient_ = wcsIqClient; + + /** + * The OAuth2 access token. + * @type {string} + * @private + */ + this.token_ = token; + + /** + * The function called when the WCS client has received a full JID. + * @type {function(): void} + * @private + */ + this.onReady_ = onReady; + + /** + * The full JID of the WCS client. + * @type {string} + * @private + */ + this.clientFullJid_ = ''; + + var that = this; + /** + * A timer that polls for an updated access token. + * @type {number} + * @private + */ + this.pollForUpdatedToken_ = setInterval( + function() { refreshToken(that.setToken_); }, + 60 * 1000); + + /** + * A function called when an IQ stanza is received. + * @type {function(string): void} + * @private + */ + this.onIq_ = function(stanza) {}; + + // Handle messages from the WcsIqClient. + this.wcsIqClient_.setOnMessage(function(msg) { that.onMessage_(msg); }); + + // Start the WcsIqClient. + this.wcsIqClient_.connectChannel(); +}; + +/** + * Passes an access token to the WcsIqClient, if the token has been updated. + * + * @param {string} tokenNew A (possibly updated) access token. + * @return {void} Nothing. + * @private + */ +remoting.Wcs.prototype.setToken_ = function(tokenNew) { + if (tokenNew != this.token_) { + this.token_ = tokenNew; + this.wcsIqClient_.updateAccessToken(this.token_); + } +}; + +/** + * Handles a message coming from the WcsIqClient. + * + * @param {Array} msg The message. + * @return {void} Nothing. + * @private + */ +remoting.Wcs.prototype.onMessage_ = function(msg) { + if (msg[0] == 'is') { + this.onIq_(msg[1]); + } else if (msg[0] == 'cfj') { + this.clientFullJid_ = msg[1]; + remoting.debug.log('Received JID: ' + this.clientFullJid_); + this.onReady_(); + this.onReady_ = function() {}; + } +}; + +/** + * Gets the full JID of the WCS client. + * + * @return {string} The full JID. + */ +remoting.Wcs.prototype.getJid = function() { + return this.clientFullJid_; +}; + +/** + * Sends an IQ stanza. + * + * @param {string} stanza An IQ stanza. + * @return {void} Nothing. + */ +remoting.Wcs.prototype.sendIq = function(stanza) { + this.wcsIqClient_.sendIq(stanza); +}; + +/** + * Sets the function called when an IQ stanza is received. + * + * @param {function(string): void} onIq The function called when an IQ stanza + * is received. + * @return {void} Nothing. + */ +remoting.Wcs.prototype.setOnIq = function(onIq) { + this.onIq_ = onIq; +}; + +}()); diff --git a/remoting/webapp/me2mom/wcs_iq_client_proto.js b/remoting/webapp/me2mom/wcs_iq_client_proto.js new file mode 100644 index 0000000..85b89c0 --- /dev/null +++ b/remoting/webapp/me2mom/wcs_iq_client_proto.js @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains type definitions for the WCS IQ client. It is used only +// with JSCompiler to verify the type-correctness of our code. + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +/** @constructor + */ +remoting.WcsIqClient = function() {}; + +/** @param {function(string): void} onMsg The function called when a message + * is received. + * @return {void} Nothing. */ +remoting.WcsIqClient.prototype.setOnMessage = function(onMsg) {}; + +/** @return {void} Nothing. */ +remoting.WcsIqClient.prototype.connectChannel = function() {}; + +/** @param {string} stanza An IQ stanza. + * @return {void} Nothing. */ +remoting.WcsIqClient.prototype.sendIq = function(stanza) {}; + +/** @param {string} token An OAuth2 access token. + * @return {void} Nothing. */ +remoting.WcsIqClient.prototype.updateAccessToken = function(token) {}; diff --git a/remoting/webapp/me2mom/wcs_loader.js b/remoting/webapp/me2mom/wcs_loader.js new file mode 100755 index 0000000..292301a --- /dev/null +++ b/remoting/webapp/me2mom/wcs_loader.js @@ -0,0 +1,139 @@ +/* Copyright (c) 2011 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/** + * @fileoverview + * A class that loads a WCS IQ client and constructs remoting.wcs as a + * wrapper for it. + */ + +'use strict'; + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function() { +/** + * @constructor + */ +remoting.WcsLoader = function() { + /** + * An OAuth2 access token. + * @type {string} + * @private + */ + this.token_ = ''; + + /** + * A callback that gets an updated access token asynchronously. + * @type {function(function(string): void): void} + * @private + */ + this.refreshToken_ = function(setToken) {}; + + /** + * The function called when WCS is ready. + * @type {function(): void} + * @private + */ + this.onReady_ = function() {}; + + /** + * @enum {string} + * @private + */ + this.LoadState_ = { + NOT_STARTED: 'NOT_STARTED', + STARTED: 'STARTED', + READY: 'READY' + }; + + /** + * The state of WCS loading. + * @type {string} + * @private + */ + this.loadState_ = this.LoadState_.NOT_STARTED; + + /** + * The WCS client that will be downloaded. + * @type {Object} + */ + this.wcsIqClient = null; +}; + +/** + * The URL of the GTalk gadget. + * @type {string} + * @private + */ +remoting.WcsLoader.prototype.TALK_GADGET_URL_ = + 'https://talkgadget.google.com/talkgadget/'; + +/** + * Starts loading the WCS IQ client. + * + * When it's loaded, construct remoting.wcs as a wrapper for it. + * When the WCS connection is ready, call |onReady|. + * + * No guarantee is made about what will happen if this function is called more + * than once. + * + * @param {string} token An OAuth2 access token. + * @param {function(function(string): void): void} refreshToken + * Gets a (possibly updated) access token asynchronously. + * @param {function(): void} onReady If the WCS connection is not yet + * ready, then |onReady| will be called when it is ready. + * @return {void} Nothing. + */ +remoting.WcsLoader.prototype.start = function(token, refreshToken, onReady) { + this.token_ = token; + this.refreshToken_ = refreshToken; + this.onReady_ = onReady; + if (this.loadState_ == this.LoadState_.READY) { + this.onReady_(); + return; + } + if (this.loadState_ == this.LoadState_.STARTED) { + return; + } + this.loadState_ = this.LoadState_.STARTED; + var node = document.createElement('script'); + node.src = this.TALK_GADGET_URL_ + 'iq?access_token=' + this.token_; + node.type = 'text/javascript'; + var that = this; + node.onload = function() { that.constructWcs_(); }; + document.body.insertBefore(node, document.body.firstChild); + return; +}; + +/** + * Constructs the remoting.wcs object. + * + * @return {void} Nothing. + * @private + */ +remoting.WcsLoader.prototype.constructWcs_ = function() { + var that = this; + remoting.wcs = new remoting.Wcs( + remoting.wcsLoader.wcsIqClient, + this.token_, + function() { that.onWcsReady_(); }, + function(setToken) { that.refreshToken_(setToken); }); +}; + +/** + * Notifies this object that WCS is ready. + * + * @return {void} Nothing. + * @private + */ +remoting.WcsLoader.prototype.onWcsReady_ = function() { + this.loadState_ = this.LoadState_.READY; + this.onReady_(); + this.onReady_ = function() {}; +}; + +}()); |