18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
28ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Copyright (c) 2010 The Chromium Authors. All rights reserved.  Use of this
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * source code is governed by a BSD-style license that can be found in the
48ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * LICENSE file.
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
68ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Constructor - no need to invoke directly, call initBackgroundPage instead.
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @constructor
108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url_request_token The OAuth request token URL.
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url_auth_token The OAuth authorize token URL.
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url_access_token The OAuth access token URL.
138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} consumer_key The OAuth consumer key.
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} consumer_secret The OAuth consumer secret.
158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} oauth_scope The OAuth scope parameter.
168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_args Optional arguments.  Recognized parameters:
178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     "app_name" {String} Name of the current application
188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *          this file was renamed to.
208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                       consumer_key, consumer_secret, oauth_scope, opt_args) {
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.url_request_token = url_request_token;
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.url_auth_token = url_auth_token;
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.url_access_token = url_access_token;
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.consumer_key = consumer_key;
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.consumer_secret = consumer_secret;
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.oauth_scope = oauth_scope;
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.app_name = opt_args && opt_args['app_name'] ||
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      "ChromeExOAuth Library";
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.key_token = "oauth_token";
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.key_token_secret = "oauth_token_secret";
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.callback_page = opt_args && opt_args['callback_page'] ||
348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      "chrome_ex_oauth.html";
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.auth_params = {};
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (opt_args && opt_args['auth_params']) {
378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    for (key in opt_args['auth_params']) {
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      if (opt_args['auth_params'].hasOwnProperty(key)) {
398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        this.auth_params[key] = opt_args['auth_params'][key];
408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/*******************************************************************************
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * PUBLIC API METHODS
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Call these from your background page.
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen ******************************************************************************/
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Initializes the OAuth helper from the background page.  You must call this
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * before attempting to make any OAuth calls.
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} oauth_config Configuration parameters in a JavaScript object.
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     The following parameters are recognized:
558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "request_url" {String} OAuth request token URL.
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "authorize_url" {String} OAuth authorize token URL.
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "access_url" {String} OAuth access token URL.
588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "consumer_key" {String} OAuth consumer key.
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "consumer_secret" {String} OAuth consumer secret.
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "scope" {String} OAuth access scope.
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "app_name" {String} Application name.
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "auth_params" {Object} Additional parameters to pass to the
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
678ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.initBackgroundPage = function(oauth_config) {
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  window.chromeExOAuthConfig = oauth_config;
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  window.chromeExOAuth = ChromeExOAuth.fromConfig(oauth_config);
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  window.chromeExOAuthRedirectStarted = false;
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  window.chromeExOAuthRequestingAccess = false;
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var url_match = chrome.extension.getURL(window.chromeExOAuth.callback_page);
748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var tabs = {};
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (changeInfo.url &&
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        changeInfo.url.substr(0, url_match.length) === url_match &&
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        changeInfo.url != tabs[tabId] &&
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        window.chromeExOAuthRequestingAccess == false) {
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      chrome.tabs.create({ 'url' : changeInfo.url }, function(tab) {
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        tabs[tab.id] = tab.url;
828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        chrome.tabs.remove(tabId);
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      });
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return window.chromeExOAuth;
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Authorizes the current user with the configued API.  You must call this
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * before calling sendSignedRequest.
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback A function to call once an access token has
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     been obtained.  This callback will be passed the following arguments:
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         token {String} The OAuth access token.
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         secret {String} The OAuth access token secret.
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
988ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.authorize = function(callback) {
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (this.hasToken()) {
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    callback(this.getToken(), this.getTokenSecret());
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } else {
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    window.chromeExOAuthOnAuthorize = function(token, secret) {
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      callback(token, secret);
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    };
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Clears any OAuth tokens stored for this configuration.  Effectively a
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * "logout" of the configured OAuth API.
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1138ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.clearTokens = function() {
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Returns whether a token is currently stored for this configuration.
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Effectively a check to see whether the current user is "logged in" to
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the configured OAuth API.
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Boolean} True if an access token exists.
1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1248ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.hasToken = function() {
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return !!this.getToken();
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Makes an OAuth-signed HTTP request with the currently authorized tokens.
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url The URL to send the request to.  Querystring parameters
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     should be omitted.
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback A function to be called once the request is
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     completed.  This callback will be passed the following arguments:
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         responseText {String} The text response.
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             send the request.  Useful if you need to check response status
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             code, etc.
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_params Additional parameters to configure the request.
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     The following parameters are accepted:
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "method" {String} The HTTP method to use.  Defaults to "GET".
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "body" {String} A request body to send.  Defaults to null.
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "parameters" {Object} Query parameters to include in the request.
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "headers" {Object} Additional headers to include in the request.
1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1458ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                                     opt_params) {
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var method = opt_params && opt_params['method'] || 'GET';
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var body = opt_params && opt_params['body'] || null;
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var params = opt_params && opt_params['parameters'] || {};
1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var headers = opt_params && opt_params['headers'] || {};
1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var signedUrl = this.signURL(url, method, params);
1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (xhr.readyState == 4) {
1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      callback(xhr.responseText, xhr);
1578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Adds the required OAuth parameters to the given url and returns the
1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * result.  Useful if you need a signed url but don't want to make an XHR
1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * request.
1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} method The http method to use.
1668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url The base url of the resource you are querying.
1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_params Query parameters to include in the request.
1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {String} The base url plus any query params plus any OAuth params.
1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1708ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var token = this.getToken();
1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var secret = this.getTokenSecret();
1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!token || !secret) {
1748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    throw new Error("No oauth token or token secret");
1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var params = opt_params || {};
1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var result = OAuthSimple().sign({
1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    action : method,
1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    path : url,
1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parameters : params,
1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signatures: {
1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      consumer_key : this.consumer_key,
1858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      shared_secret : this.consumer_secret,
1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      oauth_secret : secret,
1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      oauth_token: token
1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
1908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return result.signed_url;
1928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Generates the Authorization header based on the oauth parameters.
1968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url The base url of the resource you are querying.
1978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_params Query parameters to include in the request.
1988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {String} An Authorization header containing the oauth_* params.
1998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2008ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
2018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                                          opt_params) {
2028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var token = this.getToken();
2038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var secret = this.getTokenSecret();
2048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!token || !secret) {
2058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    throw new Error("No oauth token or token secret");
2068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
2078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var params = opt_params || {};
2098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return OAuthSimple().getHeaderString({
2118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    action: method,
2128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    path : url,
2138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parameters : params,
2148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signatures: {
2158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      consumer_key : this.consumer_key,
2168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      shared_secret : this.consumer_secret,
2178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      oauth_secret : secret,
2188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      oauth_token: token
2198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
2208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
2218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/*******************************************************************************
2248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * PRIVATE API METHODS
2258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Used by the library.  There should be no need to call these methods directly.
2268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen ******************************************************************************/
2278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Creates a new ChromeExOAuth object from the supplied configuration object.
2308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} oauth_config Configuration parameters in a JavaScript object.
2318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     The following parameters are recognized:
2328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "request_url" {String} OAuth request token URL.
2338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "authorize_url" {String} OAuth authorize token URL.
2348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "access_url" {String} OAuth access token URL.
2358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "consumer_key" {String} OAuth consumer key.
2368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "consumer_secret" {String} OAuth consumer secret.
2378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "scope" {String} OAuth access scope.
2388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "app_name" {String} Application name.
2398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "auth_params" {Object} Additional parameters to pass to the
2408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             Authorization token URL.  For an example, 'hd', 'hl', 'btmpl':
2418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
2428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
2438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2448ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.fromConfig = function(oauth_config) {
2458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return new ChromeExOAuth(
2468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['request_url'],
2478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['authorize_url'],
2488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['access_url'],
2498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['consumer_key'],
2508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['consumer_secret'],
2518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    oauth_config['scope'],
2528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    {
2538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      'app_name' : oauth_config['app_name'],
2548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      'auth_params' : oauth_config['auth_params']
2558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
2568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  );
2578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Initializes chrome_ex_oauth.html and redirects the page if needed to start
2618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the OAuth flow.  Once an access token is obtained, this function closes
2628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * chrome_ex_oauth.html.
2638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2648ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.initCallbackPage = function() {
2658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var background_page = chrome.extension.getBackgroundPage();
2668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var oauth_config = background_page.chromeExOAuthConfig;
2678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var oauth = ChromeExOAuth.fromConfig(oauth_config);
2688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  background_page.chromeExOAuthRedirectStarted = true;
2698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  oauth.initOAuthFlow(function (token, secret) {
2708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    background_page.chromeExOAuthOnAuthorize(token, secret);
2718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    background_page.chromeExOAuthRedirectStarted = false;
2728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    chrome.tabs.getSelected(null, function (tab) {
2738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      chrome.tabs.remove(tab.id);
2748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    });
2758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
2768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Sends an HTTP request.  Convenience wrapper for XMLHttpRequest calls.
2808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} method The HTTP method to use.
2818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url The URL to send the request to.
2828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} headers Optional request headers in key/value format.
2838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} body Optional body content.
2848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback Function to call when the XMLHttpRequest's
2858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     ready state changes.  See documentation for XMLHttpRequest's
2868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     onreadystatechange handler for more information.
2878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2888ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
2898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var xhr = new XMLHttpRequest();
2908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  xhr.onreadystatechange = function(data) {
2918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    callback(xhr, data);
2928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
2938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  xhr.open(method, url, true);
2948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (headers) {
2958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    for (var header in headers) {
2968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      if (headers.hasOwnProperty(header)) {
2978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        xhr.setRequestHeader(header, headers[header]);
2988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
2998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
3008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
3018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  xhr.send(body);
3028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Decodes a URL-encoded string into key/value pairs.
3068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} encoded An URL-encoded string.
3078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Object} An object representing the decoded key/value pairs found
3088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     in the encoded string.
3098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3108ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.formDecode = function(encoded) {
3118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var params = encoded.split("&");
3128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var decoded = {};
3138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  for (var i = 0, param; param = params[i]; i++) {
3148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var keyval = param.split("=");
3158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (keyval.length == 2) {
3168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var key = ChromeExOAuth.fromRfc3986(keyval[0]);
3178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var val = ChromeExOAuth.fromRfc3986(keyval[1]);
3188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      decoded[key] = val;
3198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
3208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
3218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return decoded;
3228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Returns the current window's querystring decoded into key/value pairs.
3268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Object} A object representing any key/value pairs found in the
3278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     current window's querystring.
3288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3298ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.getQueryStringParams = function() {
3308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var urlparts = window.location.href.split("?");
3318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (urlparts.length >= 2) {
3328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var querystring = urlparts.slice(1).join("?");
3338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return ChromeExOAuth.formDecode(querystring);
3348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
3358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return {};
3368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Binds a function call to a specific object.  This function will also take
3408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * a variable number of additional arguments which will be prepended to the
3418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * arguments passed to the bound function when it is called.
3428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} func The function to bind.
3438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} obj The object to bind to the function's "this".
3448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Function} A closure that will call the bound function.
3458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3468ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.bind = function(func, obj) {
3478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var newargs = Array.prototype.slice.call(arguments).slice(2);
3488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return function() {
3498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
3508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    func.apply(obj, combinedargs);
3518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
3528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Encodes a value according to the RFC3986 specification.
3568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} val The string to encode.
3578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3588ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.toRfc3986 = function(val){
3598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   return encodeURIComponent(val)
3608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen       .replace(/\!/g, "%21")
3618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen       .replace(/\*/g, "%2A")
3628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen       .replace(/'/g, "%27")
3638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen       .replace(/\(/g, "%28")
3648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen       .replace(/\)/g, "%29");
3658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Decodes a string that has been encoded according to RFC3986.
3698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} val The string to decode.
3708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3718ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.fromRfc3986 = function(val){
3728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var tmp = val
3738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      .replace(/%21/g, "!")
3748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      .replace(/%2A/g, "*")
3758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      .replace(/%27/g, "'")
3768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      .replace(/%28/g, "(")
3778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      .replace(/%29/g, ")");
3788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   return decodeURIComponent(tmp);
3798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Adds a key/value parameter to the supplied URL.
3838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} url An URL which may or may not contain querystring values.
3848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} key A key
3858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} value A value
3868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {String} The URL with URL-encoded versions of the key and value
3878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     appended, prefixing them with "&" or "?" as needed.
3888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3898ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.addURLParam = function(url, key, value) {
3908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var sep = (url.indexOf('?') >= 0) ? "&" : "?";
3918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return url + sep +
3928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen         ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
3938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Stores an OAuth token for the configured scope.
3978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} token The token to store.
3988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3998ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.setToken = function(token) {
4008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
4018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
4048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Retrieves any stored token for the configured scope.
4058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {String} The stored token.
4068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
4078ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.getToken = function() {
4088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return localStorage[this.key_token + encodeURI(this.oauth_scope)];
4098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
4128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Stores an OAuth token secret for the configured scope.
4138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} secret The secret to store.
4148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
4158ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.setTokenSecret = function(secret) {
4168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
4178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
4208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Retrieves any stored secret for the configured scope.
4218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {String} The stored secret.
4228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
4238ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.getTokenSecret = function() {
4248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
4258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
4288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Starts an OAuth authorization flow for the current page.  If a token exists,
4298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * no redirect is needed and the supplied callback is called immediately.
4308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * If this method detects that a redirect has finished, it grabs the
4318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * appropriate OAuth parameters from the URL and attempts to retrieve an
4328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * access token.  If no token exists and no redirect has happened, then
4338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * an access token is requested and the page is ultimately redirected.
4348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback The function to call once the flow has finished.
4358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     This callback will be passed the following arguments:
4368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         token {String} The OAuth access token.
4378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         secret {String} The OAuth access token secret.
4388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
4398ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.initOAuthFlow = function(callback) {
4408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!this.hasToken()) {
4418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var params = ChromeExOAuth.getQueryStringParams();
4428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (params['chromeexoauthcallback'] == 'true') {
4438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var oauth_token = params['oauth_token'];
4448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var oauth_verifier = params['oauth_verifier']
4458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      this.getAccessToken(oauth_token, oauth_verifier, callback);
4468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    } else {
4478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var request_params = {
4488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        'url_callback_param' : 'chromeexoauthcallback'
4498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
4508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      this.getRequestToken(function(url) {
4518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        window.location.href = url;
4528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }, request_params);
4538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
4548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } else {
4558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    callback(this.getToken(), this.getTokenSecret());
4568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
4578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
4608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Requests an OAuth request token.
4618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback Function to call once the authorize URL is
4628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     calculated.  This callback will be passed the following arguments:
4638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         url {String} The URL the user must be redirected to in order to
4648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             approve the token.
4658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_args Optional arguments.  The following parameters
4668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     are accepted:
4678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "url_callback" {String} The URL the OAuth provider will redirect to.
4688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         "url_callback_param" {String} A parameter to include in the callback
4698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             URL in order to indicate to this library that a redirect has
4708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             taken place.
4718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
4728ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
4738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (typeof callback !== "function") {
4748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    throw new Error("Specified callback must be a function.");
4758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
4768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var url = opt_args && opt_args['url_callback'] ||
4778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            window && window.top && window.top.location &&
4788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen            window.top.location.href;
4798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var url_param = opt_args && opt_args['url_callback_param'] ||
4818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                  "chromeexoauthcallback";
4828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
4838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
4848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var result = OAuthSimple().sign({
4858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    path : this.url_request_token,
4868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parameters: {
4878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      "xoauth_displayname" : this.app_name,
4888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      "scope" : this.oauth_scope,
4898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      "oauth_callback" : url_callback
4908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    },
4918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    signatures: {
4928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      consumer_key : this.consumer_key,
4938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      shared_secret : this.consumer_secret
4948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
4958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  });
4968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
4978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
4988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
4998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
5018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Called when a request token has been returned.  Stores the request token
5028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * secret for later use and sends the authorization url to the supplied
5038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * callback (for redirecting the user).
5048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback Function to call once the authorize URL is
5058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     calculated.  This callback will be passed the following arguments:
5068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         url {String} The URL the user must be redirected to in order to
5078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *             approve the token.
5088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
5098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     request token.
5108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
5118ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
5128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (xhr.readyState == 4) {
5138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (xhr.status == 200) {
5148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var params = ChromeExOAuth.formDecode(xhr.responseText);
5158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var token = params['oauth_token'];
5168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      this.setTokenSecret(params['oauth_token_secret']);
5178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var url = ChromeExOAuth.addURLParam(this.url_auth_token,
5188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                          "oauth_token", token);
5198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      for (var key in this.auth_params) {
5208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if (this.auth_params.hasOwnProperty(key)) {
5218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen          url = ChromeExOAuth.addURLParam(url, key, this.auth_params[key]);
5228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        }
5238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
5248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      callback(url);
5258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    } else {
5268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      throw new Error("Fetching request token failed. Status " + xhr.status);
5278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
5288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
5298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
5308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
5328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Requests an OAuth access token.
5338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} oauth_token The OAuth request token.
5348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {String} oauth_verifier The OAuth token verifier.
5358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback The function to call once the token is obtained.
5368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     This callback will be passed the following arguments:
5378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         token {String} The OAuth access token.
5388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         secret {String} The OAuth access token secret.
5398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
5408ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
5418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                                                  callback) {
5428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (typeof callback !== "function") {
5438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    throw new Error("Specified callback must be a function.");
5448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
5458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var bg = chrome.extension.getBackgroundPage();
5468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (bg.chromeExOAuthRequestingAccess == false) {
5478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    bg.chromeExOAuthRequestingAccess = true;
5488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var result = OAuthSimple().sign({
5508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      path : this.url_access_token,
5518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      parameters: {
5528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        "oauth_token" : oauth_token,
5538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        "oauth_verifier" : oauth_verifier
5548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      },
5558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      signatures: {
5568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        consumer_key : this.consumer_key,
5578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        shared_secret : this.consumer_secret,
5588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        oauth_secret : this.getTokenSecret(this.oauth_scope)
5598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
5608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    });
5618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var onToken = ChromeExOAuth.bind(this.onAccessToken, this, callback);
5638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
5648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
5658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
5668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
5678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
5688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Called when an access token has been returned.  Stores the access token and
5698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * access token secret for later use and sends them to the supplied callback.
5708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} callback The function to call once the token is obtained.
5718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     This callback will be passed the following arguments:
5728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         token {String} The OAuth access token.
5738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *         secret {String} The OAuth access token secret.
5748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
5758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *     access token.
5768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
5778ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenChromeExOAuth.prototype.onAccessToken = function(callback, xhr) {
5788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (xhr.readyState == 4) {
5798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var bg = chrome.extension.getBackgroundPage();
5808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (xhr.status == 200) {
5818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var params = ChromeExOAuth.formDecode(xhr.responseText);
5828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var token = params["oauth_token"];
5838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var secret = params["oauth_token_secret"];
5848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      this.setToken(token);
5858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      this.setTokenSecret(secret);
5868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      bg.chromeExOAuthRequestingAccess = false;
5878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      callback(token, secret);
5888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    } else {
5898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      bg.chromeExOAuthRequestingAccess = false;
5908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      throw new Error("Fetching access token failed with status " + xhr.status);
5918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
5928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
5938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
5948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
595