Index: chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js |
diff --git a/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js |
index 384616cf14e0936741a1d1402da561621063bf8a..1b62882562f2470f68f6d1347518206af9da89fc 100644 |
--- a/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js |
+++ b/chrome/common/extensions/docs/examples/extensions/gdocs/chrome_ex_oauth.js |
@@ -32,6 +32,14 @@ function ChromeExOAuth(url_request_token, url_auth_token, url_access_token, |
this.key_token_secret = "oauth_token_secret"; |
this.callback_page = opt_args && opt_args['callback_page'] || |
"chrome_ex_oauth.html"; |
+ this.auth_params = {}; |
+ if (opt_args && opt_args['auth_params']) { |
+ for (key in opt_args['auth_params']) { |
+ if (opt_args['auth_params'].hasOwnProperty(key)) { |
+ this.auth_params[key] = opt_args['auth_params'][key]; |
+ } |
+ } |
+ } |
}; |
/******************************************************************************* |
@@ -51,20 +59,26 @@ function ChromeExOAuth(url_request_token, url_auth_token, url_access_token, |
* "consumer_secret" {String} OAuth consumer secret. |
* "scope" {String} OAuth access scope. |
* "app_name" {String} Application name. |
+ * "auth_params" {Object} Additional parameters to pass to the |
+ * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': |
+ * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth |
* @return {ChromeExOAuth} An initialized ChromeExOAuth object. |
*/ |
ChromeExOAuth.initBackgroundPage = function(oauth_config) { |
window.chromeExOAuthConfig = oauth_config; |
window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config); |
window.chromeExOAuthRedirectStarted = false; |
+ window.chromeExOAuthRequestingAccess = false; |
var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page); |
+ var tabs = {}; |
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) { |
if (changeInfo.url && |
changeInfo.url.substr(0, url_match.length) === url_match && |
- !window.chromeExOAuthRedirectStarted) { |
- window.chromeExOAuthRedirectStarted = true; |
- chrome.tabs.create({ 'url' : changeInfo.url }, function() { |
+ changeInfo.url != tabs[tabId] && |
+ window.chromeExOAuthRequestingAccess == false) { |
+ chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) { |
+ tabs[tab.id] = tab.url; |
chrome.tabs.remove(tabId); |
}); |
} |
@@ -131,8 +145,8 @@ ChromeExOAuth.prototype.hasToken = function() { |
ChromeExOAuth.prototype.sendSignedRequest = function(url, callback, |
opt_params) { |
var method = opt_params && opt_params['method'] || 'GET'; |
- var params = opt_params && opt_params['parameters'] || {}; |
var body = opt_params && opt_params['body'] || null; |
+ var params = opt_params && opt_params['parameters'] || {}; |
var headers = opt_params && opt_params['headers'] || {}; |
var signedUrl = this.signURL(url, method, params); |
@@ -148,7 +162,7 @@ ChromeExOAuth.prototype.sendSignedRequest = function(url, callback, |
* Adds the required OAuth parameters to the given url and returns the |
* result. Useful if you need a signed url but don't want to make an XHR |
* request. |
- * @param {String} method The HTTP method to use. |
+ * @param {String} method The http method to use. |
* @param {String} url The base url of the resource you are querying. |
* @param {Object} opt_params Query parameters to include in the request. |
* @return {String} The base url plus any query params plus any OAuth params. |
@@ -163,7 +177,7 @@ ChromeExOAuth.prototype.signURL = function(url, method, opt_params) { |
var params = opt_params || {}; |
var result = OAuthSimple().sign({ |
- action: method, |
+ action : method, |
path : url, |
parameters : params, |
signatures: { |
@@ -222,6 +236,9 @@ ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method, |
* "consumer_secret" {String} OAuth consumer secret. |
* "scope" {String} OAuth access scope. |
* "app_name" {String} Application name. |
+ * "auth_params" {Object} Additional parameters to pass to the |
+ * Authorization token URL. For an example, 'hd', 'hl', 'btmpl': |
+ * http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth |
* @return {ChromeExOAuth} An initialized ChromeExOAuth object. |
*/ |
ChromeExOAuth.fromConfig = function(oauth_config) { |
@@ -233,7 +250,8 @@ ChromeExOAuth.fromConfig = function(oauth_config) { |
oauth_config['consumer_secret'], |
oauth_config['scope'], |
{ |
- 'app_name' : oauth_config['app_name'] |
+ 'app_name' : oauth_config['app_name'], |
+ 'auth_params' : oauth_config['auth_params'] |
} |
); |
}; |
@@ -244,13 +262,16 @@ ChromeExOAuth.fromConfig = function(oauth_config) { |
* chrome_ex_oauth.html. |
*/ |
ChromeExOAuth.initCallbackPage = function() { |
- var oauth_config = chrome.extension.getBackgroundPage().chromeExOAuthConfig; |
+ var background_page = chrome.extension.getBackgroundPage(); |
+ var oauth_config = background_page.chromeExOAuthConfig; |
var oauth = ChromeExOAuth.fromConfig(oauth_config); |
+ background_page.chromeExOAuthRedirectStarted = true; |
oauth.initOAuthFlow(function (token, secret) { |
- var background_page = chrome.extension.getBackgroundPage(); |
background_page.chromeExOAuthOnAuthorize(token, secret); |
background_page.chromeExOAuthRedirectStarted = false; |
- window.close(); |
+ chrome.tabs.getSelected(null, function (tab) { |
+ chrome.tabs.remove(tab.id); |
+ }); |
}); |
}; |
@@ -292,7 +313,9 @@ ChromeExOAuth.formDecode = function(encoded) { |
for (var i = 0, param; param = params[i]; i++) { |
var keyval = param.split("="); |
if (keyval.length == 2) { |
- decoded[decodeURIComponent(keyval[0])] = decodeURIComponent(keyval[1]); |
+ var key = ChromeExOAuth.fromRfc3986(keyval[0]); |
+ var val = ChromeExOAuth.fromRfc3986(keyval[1]); |
+ decoded[key] = val; |
} |
} |
return decoded; |
@@ -329,6 +352,33 @@ ChromeExOAuth.bind = function(func, obj) { |
}; |
/** |
+ * Encodes a value according to the RFC3986 specification. |
+ * @param {String} val The string to encode. |
+ */ |
+ChromeExOAuth.toRfc3986 = function(val){ |
+ return encodeURIComponent(val) |
+ .replace(/\!/g, "%21") |
+ .replace(/\*/g, "%2A") |
+ .replace(/'/g, "%27") |
+ .replace(/\(/g, "%28") |
+ .replace(/\)/g, "%29"); |
+}; |
+ |
+/** |
+ * Decodes a string that has been encoded according to RFC3986. |
+ * @param {String} val The string to decode. |
+ */ |
+ChromeExOAuth.fromRfc3986 = function(val){ |
+ var tmp = val |
+ .replace(/%21/g, "!") |
+ .replace(/%2A/g, "*") |
+ .replace(/%27/g, "'") |
+ .replace(/%28/g, "(") |
+ .replace(/%29/g, ")"); |
+ return decodeURIComponent(tmp); |
+}; |
+ |
+/** |
* Adds a key/value parameter to the supplied URL. |
* @param {String} url An URL which may or may not contain querystring values. |
* @param {String} key A key |
@@ -338,7 +388,8 @@ ChromeExOAuth.bind = function(func, obj) { |
*/ |
ChromeExOAuth.addURLParam = function(url, key, value) { |
var sep = (url.indexOf('?') >= 0) ? "&" : "?"; |
- return url + sep + encodeURIComponent(key) + "=" + encodeURIComponent(value); |
+ return url + sep + |
+ ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value); |
}; |
/** |
@@ -465,6 +516,11 @@ ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) { |
this.setTokenSecret(params['oauth_token_secret']); |
var url = ChromeExOAuth.addURLParam(this.url_auth_token, |
"oauth_token", token); |
+ for (var key in this.auth_params) { |
+ if (this.auth_params.hasOwnProperty(key)) { |
+ url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]); |
+ } |
+ } |
callback(url); |
} else { |
throw new Error("Fetching request token failed. Status " + xhr.status); |
@@ -486,21 +542,26 @@ ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier, |
if (typeof callback !== "function") { |
throw new Error("Specified callback must be a function."); |
} |
- var result = OAuthSimple().sign({ |
- path : this.url_access_token, |
- parameters: { |
- "oauth_token" : oauth_token, |
- "oauth_verifier" : oauth_verifier |
- }, |
- signatures: { |
- consumer_key : this.consumer_key, |
- shared_secret : this.consumer_secret, |
- oauth_secret : this.getTokenSecret(this.oauth_scope) |
- } |
- }); |
+ var bg = chrome.extension.getBackgroundPage(); |
+ if (bg.chromeExOAuthRequestingAccess == false) { |
+ bg.chromeExOAuthRequestingAccess = true; |
+ |
+ var result = OAuthSimple().sign({ |
+ path : this.url_access_token, |
+ parameters: { |
+ "oauth_token" : oauth_token, |
+ "oauth_verifier" : oauth_verifier |
+ }, |
+ signatures: { |
+ consumer_key : this.consumer_key, |
+ shared_secret : this.consumer_secret, |
+ oauth_secret : this.getTokenSecret(this.oauth_scope) |
+ } |
+ }); |
- var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback) |
- ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); |
+ var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback); |
+ ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken); |
+ } |
}; |
/** |
@@ -515,14 +576,17 @@ ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier, |
*/ |
ChromeExOAuth.prototype.onAccessToken = function(callback, xhr) { |
if (xhr.readyState == 4) { |
+ var bg = chrome.extension.getBackgroundPage(); |
if (xhr.status == 200) { |
var params = ChromeExOAuth.formDecode(xhr.responseText); |
var token = params["oauth_token"]; |
var secret = params["oauth_token_secret"]; |
this.setToken(token); |
this.setTokenSecret(secret); |
+ bg.chromeExOAuthRequestingAccess = false; |
callback(token, secret); |
} else { |
+ bg.chromeExOAuthRequestingAccess = false; |
throw new Error("Fetching access token failed with status " + xhr.status); |
} |
} |