summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsimonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 17:28:17 +0000
committersimonmorris@chromium.org <simonmorris@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-16 17:28:17 +0000
commitaa69f38cf90f4ee19d0338ab37bdc2e452d364ba (patch)
tree54c63a7f3bc835e273809327e155ef5df122e53b
parent089afea7c5ad19900c302314d8ee67df1acd7106 (diff)
downloadchromium_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.gyp2
-rw-r--r--remoting/webapp/me2mom/choice.html2
-rw-r--r--remoting/webapp/me2mom/client_session.js140
-rw-r--r--remoting/webapp/me2mom/oauth2.js14
-rw-r--r--remoting/webapp/me2mom/remoting.js46
-rwxr-xr-xremoting/webapp/me2mom/wcs.js144
-rw-r--r--remoting/webapp/me2mom/wcs_iq_client_proto.js29
-rwxr-xr-xremoting/webapp/me2mom/wcs_loader.js139
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() {};
+};
+
+}());