summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorkathyw@chromium.org <kathyw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-18 14:56:01 +0000
committerkathyw@chromium.org <kathyw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-18 14:56:01 +0000
commit1608605cc7d97ac18a2927cdb63e6d247515a585 (patch)
tree0e109d51b164c024086325aaf6fd9a6c2fec80dc /chrome/common
parent502f7fe3210f4a80ffbc4d223f20fb89b316615d (diff)
downloadchromium_src-1608605cc7d97ac18a2927cdb63e6d247515a585.zip
chromium_src-1608605cc7d97ac18a2927cdb63e6d247515a585.tar.gz
chromium_src-1608605cc7d97ac18a2927cdb63e6d247515a585.tar.bz2
Add the source code for the Google Wave Notifier extension. (The
extension is available in the gallery at https://chrome.google.com/extensions/detail/ihbcpkcoopncbdefilgpnlncpeajenmk.) TBR=bkennish TEST=none BUG=none Review URL: http://codereview.chromium.org/2838009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50239 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/128.pngbin0 -> 14567 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/16.pngbin0 -> 3043 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/48.pngbin0 -> 5909 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/64.pngbin0 -> 7402 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/authorized.pngbin0 -> 3043 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/background.html92
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.html27
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.js571
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauthsimple.js458
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/logo.pngbin0 -> 5122 bytes
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/manifest.json18
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/options.html86
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/popup.html127
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/prettyload.js101
-rw-r--r--chrome/common/extensions/docs/examples/extensions/wave/unauthorized.pngbin0 -> 3097 bytes
15 files changed, 1480 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/128.png b/chrome/common/extensions/docs/examples/extensions/wave/128.png
new file mode 100644
index 0000000..cec8dae
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/128.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/16.png b/chrome/common/extensions/docs/examples/extensions/wave/16.png
new file mode 100644
index 0000000..80a6968
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/16.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/48.png b/chrome/common/extensions/docs/examples/extensions/wave/48.png
new file mode 100644
index 0000000..06c8adc
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/48.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/64.png b/chrome/common/extensions/docs/examples/extensions/wave/64.png
new file mode 100644
index 0000000..2090cfb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/64.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/authorized.png b/chrome/common/extensions/docs/examples/extensions/wave/authorized.png
new file mode 100644
index 0000000..80a6968
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/authorized.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/background.html b/chrome/common/extensions/docs/examples/extensions/wave/background.html
new file mode 100644
index 0000000..5d015e0
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/background.html
@@ -0,0 +1,92 @@
+<!--
+ Copyright 2010 Google
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+
+ Brian Kennish <byoogle@google.com>
+-->
+<script src="chrome_ex_oauthsimple.js"></script>
+<script src="chrome_ex_oauth.js"></script>
+<script src="prettyload.js"></script>
+<script>
+ function parse(type) {
+ return typeof type == 'string' ? JSON.parse(type) : type;
+ }
+
+ function initialize() { localStorage.unreadCount = UNKNOWN_COUNT; }
+
+ function load() {
+ BROWSER_ACTION.setBadgeBackgroundColor({color: [139, 139, 139, 255]});
+ }
+
+ function sync() {
+ PRETTYLOAD.start(localStorage.unreadCount);
+
+ OAUTH.sendSignedRequest(
+ 'http://www-opensocial.googleusercontent.com/api/rpc',
+ function(response) {
+ const RESULTS = JSON.parse(response).data.searchResults;
+ localStorage.digests = JSON.stringify(RESULTS.digests.slice(0, 20));
+ const UNREAD_COUNT = RESULTS.numResults;
+ PRETTYLOAD.finish(
+ localStorage.unreadCount =
+ UNREAD_COUNT < MAX_UNREAD ? UNREAD_COUNT + '' : MAX_UNREAD - 1 + '+'
+ );
+ },
+ {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ method: 'wave.robot.search',
+ params: {query: 'in:inbox is:unread', numResults: MAX_UNREAD}
+ })
+ }
+ );
+ }
+
+ function authorize() {
+ OAUTH.authorize(function() {
+ BROWSER_ACTION.setIcon({path: 'authorized.png'});
+ BROWSER_ACTION.setBadgeBackgroundColor({color: [85, 144, 210, 255]});
+ sync();
+
+ id = setInterval(function() { sync(); }, 60000);
+ });
+ }
+
+ const UNKNOWN_COUNT = '?';
+
+ if (!parse(localStorage.initialized)) {
+ localStorage.maxDigests = 5;
+ localStorage.participantAnnotated = true;
+ initialize();
+ localStorage.initialized = true;
+ }
+
+ const BROWSER_ACTION = chrome.browserAction;
+ load();
+ BROWSER_ACTION.setBadgeText({text: UNKNOWN_COUNT});
+ const OAUTH = ChromeExOAuth.initBackgroundPage({
+ request_url: 'https://www.google.com/accounts/OAuthGetRequestToken',
+ authorize_url: 'https://www.google.com/accounts/OAuthAuthorizeToken',
+ access_url: 'https://www.google.com/accounts/OAuthGetAccessToken',
+ consumer_key: 'anonymous',
+ consumer_secret: 'anonymous',
+ scope: 'http://wave.googleusercontent.com/api/rpc',
+ app_name: 'Google Wave Notifier'
+ });
+ var id;
+ const PRETTYLOAD = new Prettyload(TRIANGLE_WAVE);
+ const MAX_UNREAD = 100;
+ authorize();
+</script>
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.html b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.html
new file mode 100644
index 0000000..912f891
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!--
+ * Copyright (c) 2009 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.
+-->
+<html>
+ <head>
+ <title>OAuth Redirect Page</title>
+ <style type="text/css">
+ body {
+ font: 16px Arial;
+ color: #333;
+ }
+ </style>
+ <script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
+ <script type="text/javascript" src="chrome_ex_oauth.js"></script>
+ <script type="text/javascript">
+ function onLoad() {
+ ChromeExOAuth.initCallbackPage();
+ };
+ </script>
+ </head>
+ <body onload="onLoad();">
+ Redirecting...
+ </body>
+</html>
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.js b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.js
new file mode 100644
index 0000000..f28111a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauth.js
@@ -0,0 +1,571 @@
+/**
+ * Copyright (c) 2010 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.
+ */
+
+/**
+ * Constructor - no need to invoke directly, call initBackgroundPage instead.
+ * @constructor
+ * @param {String} url_request_token The OAuth request token URL.
+ * @param {String} url_auth_token The OAuth authorize token URL.
+ * @param {String} url_access_token The OAuth access token URL.
+ * @param {String} consumer_key The OAuth consumer key.
+ * @param {String} consumer_secret The OAuth consumer secret.
+ * @param {String} oauth_scope The OAuth scope parameter.
+ * @param {Object} opt_args Optional arguments. Recognized parameters:
+ * "app_name" {String} Name of the current application
+ * "callback_page" {String} If you renamed chrome_ex_oauth.html, the name
+ * this file was renamed to.
+ */
+function ChromeExOAuth(url_request_token, url_auth_token, url_access_token,
+ consumer_key, consumer_secret, oauth_scope, opt_args) {
+ this.url_request_token = url_request_token;
+ this.url_auth_token = url_auth_token;
+ this.url_access_token = url_access_token;
+ this.consumer_key = consumer_key;
+ this.consumer_secret = consumer_secret;
+ this.oauth_scope = oauth_scope;
+ this.app_name = opt_args && opt_args['app_name'] ||
+ "ChromeExOAuth Library";
+ this.key_token = "oauth_token";
+ this.key_token_secret = "oauth_token_secret";
+ this.callback_page = opt_args && opt_args['callback_page'] ||
+ "chrome_ex_oauth.html";
+};
+
+/*******************************************************************************
+ * PUBLIC API METHODS
+ * Call these from your background page.
+ ******************************************************************************/
+
+/**
+ * Initializes the OAuth helper from the background page. You must call this
+ * before attempting to make any OAuth calls.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ * The following parameters are recognized:
+ * "request_url" {String} OAuth request token URL.
+ * "authorize_url" {String} OAuth authorize token URL.
+ * "access_url" {String} OAuth access token URL.
+ * "consumer_key" {String} OAuth consumer key.
+ * "consumer_secret" {String} OAuth consumer secret.
+ * "scope" {String} OAuth access scope.
+ * "app_name" {String} Application name.
+ * @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);
+ chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+ if (changeInfo.url &&
+ changeInfo.url.substr(0, url_match.length) === url_match &&
+ window.chromeExOAuthRequestingAccess == false) {
+ chrome.tabs.create({ 'url' : changeInfo.url }, function() {
+ chrome.tabs.remove(tabId);
+ });
+ }
+ });
+
+ return window.chromeExOAuth;
+};
+
+/**
+ * Authorizes the current user with the configued API. You must call this
+ * before calling sendSignedRequest.
+ * @param {Function} callback A function to call once an access token has
+ * been obtained. This callback will be passed the following arguments:
+ * token {String} The OAuth access token.
+ * secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.authorize = function(callback) {
+ if (this.hasToken()) {
+ callback(this.getToken(), this.getTokenSecret());
+ } else {
+ window.chromeExOAuthOnAuthorize = function(token, secret) {
+ callback(token, secret);
+ };
+ chrome.tabs.create({ 'url' :chrome.extension.getURL(this.callback_page) });
+ }
+};
+
+/**
+ * Clears any OAuth tokens stored for this configuration. Effectively a
+ * "logout" of the configured OAuth API.
+ */
+ChromeExOAuth.prototype.clearTokens = function() {
+ delete localStorage[this.key_token + encodeURI(this.oauth_scope)];
+ delete localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Returns whether a token is currently stored for this configuration.
+ * Effectively a check to see whether the current user is "logged in" to
+ * the configured OAuth API.
+ * @return {Boolean} True if an access token exists.
+ */
+ChromeExOAuth.prototype.hasToken = function() {
+ return !!this.getToken();
+};
+
+/**
+ * Makes an OAuth-signed HTTP request with the currently authorized tokens.
+ * @param {String} url The URL to send the request to. Querystring parameters
+ * should be omitted.
+ * @param {Function} callback A function to be called once the request is
+ * completed. This callback will be passed the following arguments:
+ * responseText {String} The text response.
+ * xhr {XMLHttpRequest} The XMLHttpRequest object which was used to
+ * send the request. Useful if you need to check response status
+ * code, etc.
+ * @param {Object} opt_params Additional parameters to configure the request.
+ * The following parameters are accepted:
+ * "method" {String} The HTTP method to use. Defaults to "GET".
+ * "body" {String} A request body to send. Defaults to null.
+ * "parameters" {Object} Query parameters to include in the request.
+ * "headers" {Object} Additional headers to include in the request.
+ */
+ChromeExOAuth.prototype.sendSignedRequest = function(url, callback,
+ opt_params) {
+ var method = opt_params && opt_params['method'] || 'GET';
+ 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);
+
+ ChromeExOAuth.sendRequest(method, signedUrl, headers, body, function (xhr) {
+ if (xhr.readyState == 4) {
+ callback(xhr.responseText, xhr);
+ }
+ });
+};
+
+/**
+ * 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} 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.
+ */
+ChromeExOAuth.prototype.signURL = function(url, method, opt_params) {
+ var token = this.getToken();
+ var secret = this.getTokenSecret();
+ if (!token || !secret) {
+ throw new Error("No oauth token or token secret");
+ }
+
+ var params = opt_params || {};
+
+ var result = OAuthSimple().sign({
+ action : method,
+ path : url,
+ parameters : params,
+ signatures: {
+ consumer_key : this.consumer_key,
+ shared_secret : this.consumer_secret,
+ oauth_secret : secret,
+ oauth_token: token
+ }
+ });
+
+ return result.signed_url;
+};
+
+/**
+ * Generates the Authorization header based on the oauth parameters.
+ * @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} An Authorization header containing the oauth_* params.
+ */
+ChromeExOAuth.prototype.getAuthorizationHeader = function(url, method,
+ opt_params) {
+ var token = this.getToken();
+ var secret = this.getTokenSecret();
+ if (!token || !secret) {
+ throw new Error("No oauth token or token secret");
+ }
+
+ var params = opt_params || {};
+
+ return OAuthSimple().getHeaderString({
+ action: method,
+ path : url,
+ parameters : params,
+ signatures: {
+ consumer_key : this.consumer_key,
+ shared_secret : this.consumer_secret,
+ oauth_secret : secret,
+ oauth_token: token
+ }
+ });
+};
+
+/*******************************************************************************
+ * PRIVATE API METHODS
+ * Used by the library. There should be no need to call these methods directly.
+ ******************************************************************************/
+
+/**
+ * Creates a new ChromeExOAuth object from the supplied configuration object.
+ * @param {Object} oauth_config Configuration parameters in a JavaScript object.
+ * The following parameters are recognized:
+ * "request_url" {String} OAuth request token URL.
+ * "authorize_url" {String} OAuth authorize token URL.
+ * "access_url" {String} OAuth access token URL.
+ * "consumer_key" {String} OAuth consumer key.
+ * "consumer_secret" {String} OAuth consumer secret.
+ * "scope" {String} OAuth access scope.
+ * "app_name" {String} Application name.
+ * @return {ChromeExOAuth} An initialized ChromeExOAuth object.
+ */
+ChromeExOAuth.fromConfig = function(oauth_config) {
+ return new ChromeExOAuth(
+ oauth_config['request_url'],
+ oauth_config['authorize_url'],
+ oauth_config['access_url'],
+ oauth_config['consumer_key'],
+ oauth_config['consumer_secret'],
+ oauth_config['scope'],
+ {
+ 'app_name' : oauth_config['app_name']
+ }
+ );
+};
+
+/**
+ * Initializes chrome_ex_oauth.html and redirects the page if needed to start
+ * the OAuth flow. Once an access token is obtained, this function closes
+ * chrome_ex_oauth.html.
+ */
+ChromeExOAuth.initCallbackPage = function() {
+ 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) {
+ background_page.chromeExOAuthOnAuthorize(token, secret);
+ background_page.chromeExOAuthRedirectStarted = false;
+ chrome.tabs.getSelected(null, function (tab) {
+ chrome.tabs.remove(tab.id);
+ });
+ });
+};
+
+/**
+ * Sends an HTTP request. Convenience wrapper for XMLHttpRequest calls.
+ * @param {String} method The HTTP method to use.
+ * @param {String} url The URL to send the request to.
+ * @param {Object} headers Optional request headers in key/value format.
+ * @param {String} body Optional body content.
+ * @param {Function} callback Function to call when the XMLHttpRequest's
+ * ready state changes. See documentation for XMLHttpRequest's
+ * onreadystatechange handler for more information.
+ */
+ChromeExOAuth.sendRequest = function(method, url, headers, body, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function(data) {
+ callback(xhr, data);
+ }
+ xhr.open(method, url, true);
+ if (headers) {
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)) {
+ xhr.setRequestHeader(header, headers[header]);
+ }
+ }
+ }
+ xhr.send(body);
+};
+
+/**
+ * Decodes a URL-encoded string into key/value pairs.
+ * @param {String} encoded An URL-encoded string.
+ * @return {Object} An object representing the decoded key/value pairs found
+ * in the encoded string.
+ */
+ChromeExOAuth.formDecode = function(encoded) {
+ var params = encoded.split("&");
+ var decoded = {};
+ for (var i = 0, param; param = params[i]; i++) {
+ var keyval = param.split("=");
+ if (keyval.length == 2) {
+ var key = ChromeExOAuth.fromRfc3986(keyval[0]);
+ var val = ChromeExOAuth.fromRfc3986(keyval[1]);
+ decoded[key] = val;
+ }
+ }
+ return decoded;
+};
+
+/**
+ * Returns the current window's querystring decoded into key/value pairs.
+ * @return {Object} A object representing any key/value pairs found in the
+ * current window's querystring.
+ */
+ChromeExOAuth.getQueryStringParams = function() {
+ var urlparts = window.location.href.split("?");
+ if (urlparts.length >= 2) {
+ var querystring = urlparts.slice(1).join("?");
+ return ChromeExOAuth.formDecode(querystring);
+ }
+ return {};
+};
+
+/**
+ * Binds a function call to a specific object. This function will also take
+ * a variable number of additional arguments which will be prepended to the
+ * arguments passed to the bound function when it is called.
+ * @param {Function} func The function to bind.
+ * @param {Object} obj The object to bind to the function's "this".
+ * @return {Function} A closure that will call the bound function.
+ */
+ChromeExOAuth.bind = function(func, obj) {
+ var newargs = Array.prototype.slice.call(arguments).slice(2);
+ return function() {
+ var combinedargs = newargs.concat(Array.prototype.slice.call(arguments));
+ func.apply(obj, combinedargs);
+ };
+};
+
+/**
+ * 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
+ * @param {String} value A value
+ * @return {String} The URL with URL-encoded versions of the key and value
+ * appended, prefixing them with "&" or "?" as needed.
+ */
+ChromeExOAuth.addURLParam = function(url, key, value) {
+ var sep = (url.indexOf('?') >= 0) ? "&" : "?";
+ return url + sep +
+ ChromeExOAuth.toRfc3986(key) + "=" + ChromeExOAuth.toRfc3986(value);
+};
+
+/**
+ * Stores an OAuth token for the configured scope.
+ * @param {String} token The token to store.
+ */
+ChromeExOAuth.prototype.setToken = function(token) {
+ localStorage[this.key_token + encodeURI(this.oauth_scope)] = token;
+};
+
+/**
+ * Retrieves any stored token for the configured scope.
+ * @return {String} The stored token.
+ */
+ChromeExOAuth.prototype.getToken = function() {
+ return localStorage[this.key_token + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Stores an OAuth token secret for the configured scope.
+ * @param {String} secret The secret to store.
+ */
+ChromeExOAuth.prototype.setTokenSecret = function(secret) {
+ localStorage[this.key_token_secret + encodeURI(this.oauth_scope)] = secret;
+};
+
+/**
+ * Retrieves any stored secret for the configured scope.
+ * @return {String} The stored secret.
+ */
+ChromeExOAuth.prototype.getTokenSecret = function() {
+ return localStorage[this.key_token_secret + encodeURI(this.oauth_scope)];
+};
+
+/**
+ * Starts an OAuth authorization flow for the current page. If a token exists,
+ * no redirect is needed and the supplied callback is called immediately.
+ * If this method detects that a redirect has finished, it grabs the
+ * appropriate OAuth parameters from the URL and attempts to retrieve an
+ * access token. If no token exists and no redirect has happened, then
+ * an access token is requested and the page is ultimately redirected.
+ * @param {Function} callback The function to call once the flow has finished.
+ * This callback will be passed the following arguments:
+ * token {String} The OAuth access token.
+ * secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.initOAuthFlow = function(callback) {
+ if (!this.hasToken()) {
+ var params = ChromeExOAuth.getQueryStringParams();
+ if (params['chromeexoauthcallback'] == 'true') {
+ var oauth_token = params['oauth_token'];
+ var oauth_verifier = params['oauth_verifier']
+ this.getAccessToken(oauth_token, oauth_verifier, callback);
+ } else {
+ var request_params = {
+ 'url_callback_param' : 'chromeexoauthcallback'
+ }
+ this.getRequestToken(function(url) {
+ window.location.href = url;
+ }, request_params);
+ }
+ } else {
+ callback(this.getToken(), this.getTokenSecret());
+ }
+};
+
+/**
+ * Requests an OAuth request token.
+ * @param {Function} callback Function to call once the authorize URL is
+ * calculated. This callback will be passed the following arguments:
+ * url {String} The URL the user must be redirected to in order to
+ * approve the token.
+ * @param {Object} opt_args Optional arguments. The following parameters
+ * are accepted:
+ * "url_callback" {String} The URL the OAuth provider will redirect to.
+ * "url_callback_param" {String} A parameter to include in the callback
+ * URL in order to indicate to this library that a redirect has
+ * taken place.
+ */
+ChromeExOAuth.prototype.getRequestToken = function(callback, opt_args) {
+ if (typeof callback !== "function") {
+ throw new Error("Specified callback must be a function.");
+ }
+ var url = opt_args && opt_args['url_callback'] ||
+ window && window.top && window.top.location &&
+ window.top.location.href;
+
+ var url_param = opt_args && opt_args['url_callback_param'] ||
+ "chromeexoauthcallback";
+ var url_callback = ChromeExOAuth.addURLParam(url, url_param, "true");
+
+ var result = OAuthSimple().sign({
+ path : this.url_request_token,
+ parameters: {
+ "xoauth_displayname" : this.app_name,
+ "scope" : this.oauth_scope,
+ "oauth_callback" : url_callback
+ },
+ signatures: {
+ consumer_key : this.consumer_key,
+ shared_secret : this.consumer_secret
+ }
+ });
+ var onToken = ChromeExOAuth.bind(this.onRequestToken, this, callback);
+ ChromeExOAuth.sendRequest("GET", result.signed_url, null, null, onToken);
+};
+
+/**
+ * Called when a request token has been returned. Stores the request token
+ * secret for later use and sends the authorization url to the supplied
+ * callback (for redirecting the user).
+ * @param {Function} callback Function to call once the authorize URL is
+ * calculated. This callback will be passed the following arguments:
+ * url {String} The URL the user must be redirected to in order to
+ * approve the token.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ * request token.
+ */
+ChromeExOAuth.prototype.onRequestToken = function(callback, xhr) {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ var params = ChromeExOAuth.formDecode(xhr.responseText);
+ var token = params['oauth_token'];
+ this.setTokenSecret(params['oauth_token_secret']);
+ var url = ChromeExOAuth.addURLParam(this.url_auth_token,
+ "oauth_token", token);
+ callback(url);
+ } else {
+ throw new Error("Fetching request token failed. Status " + xhr.status);
+ }
+ }
+};
+
+/**
+ * Requests an OAuth access token.
+ * @param {String} oauth_token The OAuth request token.
+ * @param {String} oauth_verifier The OAuth token verifier.
+ * @param {Function} callback The function to call once the token is obtained.
+ * This callback will be passed the following arguments:
+ * token {String} The OAuth access token.
+ * secret {String} The OAuth access token secret.
+ */
+ChromeExOAuth.prototype.getAccessToken = function(oauth_token, oauth_verifier,
+ callback) {
+ if (typeof callback !== "function") {
+ throw new Error("Specified callback must be a function.");
+ }
+ 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);
+ }
+};
+
+/**
+ * Called when an access token has been returned. Stores the access token and
+ * access token secret for later use and sends them to the supplied callback.
+ * @param {Function} callback The function to call once the token is obtained.
+ * This callback will be passed the following arguments:
+ * token {String} The OAuth access token.
+ * secret {String} The OAuth access token secret.
+ * @param {XMLHttpRequest} xhr The XMLHttpRequest object used to fetch the
+ * access token.
+ */
+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);
+ }
+ }
+};
+
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauthsimple.js b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauthsimple.js
new file mode 100644
index 0000000..af0fe8a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/chrome_ex_oauthsimple.js
@@ -0,0 +1,458 @@
+/* OAuthSimple
+ * A simpler version of OAuth
+ *
+ * author: jr conlin
+ * mail: src@anticipatr.com
+ * copyright: unitedHeroes.net
+ * version: 1.0
+ * url: http://unitedHeroes.net/OAuthSimple
+ *
+ * Copyright (c) 2009, unitedHeroes.net
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the unitedHeroes.net nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY UNITEDHEROES.NET ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL UNITEDHEROES.NET BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+var OAuthSimple;
+
+if (OAuthSimple === undefined)
+{
+ /* Simple OAuth
+ *
+ * This class only builds the OAuth elements, it does not do the actual
+ * transmission or reception of the tokens. It does not validate elements
+ * of the token. It is for client use only.
+ *
+ * api_key is the API key, also known as the OAuth consumer key
+ * shared_secret is the shared secret (duh).
+ *
+ * Both the api_key and shared_secret are generally provided by the site
+ * offering OAuth services. You need to specify them at object creation
+ * because nobody <explative>ing uses OAuth without that minimal set of
+ * signatures.
+ *
+ * If you want to use the higher order security that comes from the
+ * OAuth token (sorry, I don't provide the functions to fetch that because
+ * sites aren't horribly consistent about how they offer that), you need to
+ * pass those in either with .setTokensAndSecrets() or as an argument to the
+ * .sign() or .getHeaderString() functions.
+ *
+ * Example:
+ <code>
+ var oauthObject = OAuthSimple().sign({path:'http://example.com/rest/',
+ parameters: 'foo=bar&gorp=banana',
+ signatures:{
+ api_key:'12345abcd',
+ shared_secret:'xyz-5309'
+ }});
+ document.getElementById('someLink').href=oauthObject.signed_url;
+ </code>
+ *
+ * that will sign as a "GET" using "SHA1-MAC" the url. If you need more than
+ * that, read on, McDuff.
+ */
+
+ /** OAuthSimple creator
+ *
+ * Create an instance of OAuthSimple
+ *
+ * @param api_key {string} The API Key (sometimes referred to as the consumer key) This value is usually supplied by the site you wish to use.
+ * @param shared_secret (string) The shared secret. This value is also usually provided by the site you wish to use.
+ */
+ OAuthSimple = function (consumer_key,shared_secret)
+ {
+/* if (api_key == undefined)
+ throw("Missing argument: api_key (oauth_consumer_key) for OAuthSimple. This is usually provided by the hosting site.");
+ if (shared_secret == undefined)
+ throw("Missing argument: shared_secret (shared secret) for OAuthSimple. This is usually provided by the hosting site.");
+*/ this._secrets={};
+ this._parameters={};
+
+ // General configuration options.
+ if (consumer_key !== undefined) {
+ this._secrets['consumer_key'] = consumer_key;
+ }
+ if (shared_secret !== undefined) {
+ this._secrets['shared_secret'] = shared_secret;
+ }
+ this._default_signature_method= "HMAC-SHA1";
+ this._action = "GET";
+ this._nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+
+ this.reset = function() {
+ this._parameters={};
+ this._path=undefined;
+ return this;
+ };
+
+ /** set the parameters either from a hash or a string
+ *
+ * @param {string,object} List of parameters for the call, this can either be a URI string (e.g. "foo=bar&gorp=banana" or an object/hash)
+ */
+ this.setParameters = function (parameters) {
+ if (parameters === undefined) {
+ parameters = {};
+ }
+ if (typeof(parameters) == 'string') {
+ parameters=this._parseParameterString(parameters);
+ }
+ this._parameters = parameters;
+ if (this._parameters['oauth_nonce'] === undefined) {
+ this._getNonce();
+ }
+ if (this._parameters['oauth_timestamp'] === undefined) {
+ this._getTimestamp();
+ }
+ if (this._parameters['oauth_method'] === undefined) {
+ this.setSignatureMethod();
+ }
+ if (this._parameters['oauth_consumer_key'] === undefined) {
+ this._getApiKey();
+ }
+ if(this._parameters['oauth_token'] === undefined) {
+ this._getAccessToken();
+ }
+
+ return this;
+ };
+
+ /** convienence method for setParameters
+ *
+ * @param parameters {string,object} See .setParameters
+ */
+ this.setQueryString = function (parameters) {
+ return this.setParameters(parameters);
+ };
+
+ /** Set the target URL (does not include the parameters)
+ *
+ * @param path {string} the fully qualified URI (excluding query arguments) (e.g "http://example.org/foo")
+ */
+ this.setURL = function (path) {
+ if (path == '') {
+ throw ('No path specified for OAuthSimple.setURL');
+ }
+ this._path = path;
+ return this;
+ };
+
+ /** convienence method for setURL
+ *
+ * @param path {string} see .setURL
+ */
+ this.setPath = function(path){
+ return this.setURL(path);
+ };
+
+ /** set the "action" for the url, (e.g. GET,POST, DELETE, etc.)
+ *
+ * @param action {string} HTTP Action word.
+ */
+ this.setAction = function(action) {
+ if (action === undefined) {
+ action="GET";
+ }
+ action = action.toUpperCase();
+ if (action.match('[^A-Z]')) {
+ throw ('Invalid action specified for OAuthSimple.setAction');
+ }
+ this._action = action;
+ return this;
+ };
+
+ /** set the signatures (as well as validate the ones you have)
+ *
+ * @param signatures {object} object/hash of the token/signature pairs {api_key:, shared_secret:, oauth_token: oauth_secret:}
+ */
+ this.setTokensAndSecrets = function(signatures) {
+ if (signatures)
+ {
+ for (var i in signatures) {
+ this._secrets[i] = signatures[i];
+ }
+ }
+ // Aliases
+ if (this._secrets['api_key']) {
+ this._secrets.consumer_key = this._secrets.api_key;
+ }
+ if (this._secrets['access_token']) {
+ this._secrets.oauth_token = this._secrets.access_token;
+ }
+ if (this._secrets['access_secret']) {
+ this._secrets.oauth_secret = this._secrets.access_secret;
+ }
+ // Gauntlet
+ if (this._secrets.consumer_key === undefined) {
+ throw('Missing required consumer_key in OAuthSimple.setTokensAndSecrets');
+ }
+ if (this._secrets.shared_secret === undefined) {
+ throw('Missing required shared_secret in OAuthSimple.setTokensAndSecrets');
+ }
+ if ((this._secrets.oauth_token !== undefined) && (this._secrets.oauth_secret === undefined)) {
+ throw('Missing oauth_secret for supplied oauth_token in OAuthSimple.setTokensAndSecrets');
+ }
+ return this;
+ };
+
+ /** set the signature method (currently only Plaintext or SHA-MAC1)
+ *
+ * @param method {string} Method of signing the transaction (only PLAINTEXT and SHA-MAC1 allowed for now)
+ */
+ this.setSignatureMethod = function(method) {
+ if (method === undefined) {
+ method = this._default_signature_method;
+ }
+ //TODO: accept things other than PlainText or SHA-MAC1
+ if (method.toUpperCase().match(/(PLAINTEXT|HMAC-SHA1)/) === undefined) {
+ throw ('Unknown signing method specified for OAuthSimple.setSignatureMethod');
+ }
+ this._parameters['oauth_signature_method']= method.toUpperCase();
+ return this;
+ };
+
+ /** sign the request
+ *
+ * note: all arguments are optional, provided you've set them using the
+ * other helper functions.
+ *
+ * @param args {object} hash of arguments for the call
+ * {action:, path:, parameters:, method:, signatures:}
+ * all arguments are optional.
+ */
+ this.sign = function (args) {
+ if (args === undefined) {
+ args = {};
+ }
+ // Set any given parameters
+ if(args['action'] !== undefined) {
+ this.setAction(args['action']);
+ }
+ if (args['path'] !== undefined) {
+ this.setPath(args['path']);
+ }
+ if (args['method'] !== undefined) {
+ this.setSignatureMethod(args['method']);
+ }
+ this.setTokensAndSecrets(args['signatures']);
+ if (args['parameters'] !== undefined){
+ this.setParameters(args['parameters']);
+ }
+ // check the parameters
+ var normParams = this._normalizedParameters();
+ this._parameters['oauth_signature']=this._generateSignature(normParams);
+ return {
+ parameters: this._parameters,
+ signature: this._oauthEscape(this._parameters['oauth_signature']),
+ signed_url: this._path + '?' + this._normalizedParameters(),
+ header: this.getHeaderString()
+ };
+ };
+
+ /** Return a formatted "header" string
+ *
+ * NOTE: This doesn't set the "Authorization: " prefix, which is required.
+ * I don't set it because various set header functions prefer different
+ * ways to do that.
+ *
+ * @param args {object} see .sign
+ */
+ this.getHeaderString = function(args) {
+ if (this._parameters['oauth_signature'] === undefined) {
+ this.sign(args);
+ }
+
+ var result = 'OAuth ';
+ for (var pName in this._parameters)
+ {
+ if (!pName.match(/^oauth/)) {
+ continue;
+ }
+ if ((this._parameters[pName]) instanceof Array)
+ {
+ var pLength = this._parameters[pName].length;
+ for (var j=0;j<pLength;j++)
+ {
+ result += pName +'="'+this._oauthEscape(this._parameters[pName][j])+'" ';
+ }
+ }
+ else
+ {
+ result += pName + '="'+this._oauthEscape(this._parameters[pName])+'" ';
+ }
+ }
+ return result;
+ };
+
+ // Start Private Methods.
+
+ /** convert the parameter string into a hash of objects.
+ *
+ */
+ this._parseParameterString = function(paramString){
+ var elements = paramString.split('&');
+ var result={};
+ for(var element=elements.shift();element;element=elements.shift())
+ {
+ var keyToken=element.split('=');
+ var value='';
+ if (keyToken[1]) {
+ value=decodeURIComponent(keyToken[1]);
+ }
+ if(result[keyToken[0]]){
+ if (!(result[keyToken[0]] instanceof Array))
+ {
+ result[keyToken[0]] = Array(result[keyToken[0]],value);
+ }
+ else
+ {
+ result[keyToken[0]].push(value);
+ }
+ }
+ else
+ {
+ result[keyToken[0]]=value;
+ }
+ }
+ return result;
+ };
+
+ this._oauthEscape = function(string) {
+ if (string === undefined) {
+ return "";
+ }
+ if (string instanceof Array)
+ {
+ throw('Array passed to _oauthEscape');
+ }
+ return encodeURIComponent(string).replace(/\!/g, "%21").
+ replace(/\*/g, "%2A").
+ replace(/'/g, "%27").
+ replace(/\(/g, "%28").
+ replace(/\)/g, "%29");
+ };
+
+ this._getNonce = function (length) {
+ if (length === undefined) {
+ length=5;
+ }
+ var result = "";
+ var cLength = this._nonce_chars.length;
+ for (var i = 0; i < length;i++) {
+ var rnum = Math.floor(Math.random() *cLength);
+ result += this._nonce_chars.substring(rnum,rnum+1);
+ }
+ this._parameters['oauth_nonce']=result;
+ return result;
+ };
+
+ this._getApiKey = function() {
+ if (this._secrets.consumer_key === undefined) {
+ throw('No consumer_key set for OAuthSimple.');
+ }
+ this._parameters['oauth_consumer_key']=this._secrets.consumer_key;
+ return this._parameters.oauth_consumer_key;
+ };
+
+ this._getAccessToken = function() {
+ if (this._secrets['oauth_secret'] === undefined) {
+ return '';
+ }
+ if (this._secrets['oauth_token'] === undefined) {
+ throw('No oauth_token (access_token) set for OAuthSimple.');
+ }
+ this._parameters['oauth_token'] = this._secrets.oauth_token;
+ return this._parameters.oauth_token;
+ };
+
+ this._getTimestamp = function() {
+ var d = new Date();
+ var ts = Math.floor(d.getTime()/1000);
+ this._parameters['oauth_timestamp'] = ts;
+ return ts;
+ };
+
+ this.b64_hmac_sha1 = function(k,d,_p,_z){
+ // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js
+ // _p = b64pad, _z = character size; not used here but I left them available just in case
+ if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
+ }
+
+
+ this._normalizedParameters = function() {
+ var elements = new Array();
+ var paramNames = [];
+ var ra =0;
+ for (var paramName in this._parameters)
+ {
+ if (ra++ > 1000) {
+ throw('runaway 1');
+ }
+ paramNames.unshift(paramName);
+ }
+ paramNames = paramNames.sort();
+ pLen = paramNames.length;
+ for (var i=0;i<pLen; i++)
+ {
+ paramName=paramNames[i];
+ //skip secrets.
+ if (paramName.match(/\w+_secret/)) {
+ continue;
+ }
+ if (this._parameters[paramName] instanceof Array)
+ {
+ var sorted = this._parameters[paramName].sort();
+ var spLen = sorted.length;
+ for (var j = 0;j<spLen;j++){
+ if (ra++ > 1000) {
+ throw('runaway 1');
+ }
+ elements.push(this._oauthEscape(paramName) + '=' +
+ this._oauthEscape(sorted[j]));
+ }
+ continue;
+ }
+ elements.push(this._oauthEscape(paramName) + '=' +
+ this._oauthEscape(this._parameters[paramName]));
+ }
+ return elements.join('&');
+ };
+
+ this._generateSignature = function() {
+
+ var secretKey = this._oauthEscape(this._secrets.shared_secret)+'&'+
+ this._oauthEscape(this._secrets.oauth_secret);
+ if (this._parameters['oauth_signature_method'] == 'PLAINTEXT')
+ {
+ return secretKey;
+ }
+ if (this._parameters['oauth_signature_method'] == 'HMAC-SHA1')
+ {
+ var sigString = this._oauthEscape(this._action)+'&'+this._oauthEscape(this._path)+'&'+this._oauthEscape(this._normalizedParameters());
+ return this.b64_hmac_sha1(secretKey,sigString);
+ }
+ return null;
+ };
+
+ return this;
+ };
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/logo.png b/chrome/common/extensions/docs/examples/extensions/wave/logo.png
new file mode 100644
index 0000000..6593b01
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/logo.png
Binary files differ
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/manifest.json b/chrome/common/extensions/docs/examples/extensions/wave/manifest.json
new file mode 100644
index 0000000..34a22f7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Google Wave Notifier",
+ "version": "1.0.0",
+ "description": "Find out when you have new waves and preview them fast.",
+ "icons": {"16": "16.png", "48": "48.png", "128": "128.png"},
+ "permissions": [
+ "tabs",
+ "https://www.google.com/",
+ "http://www-opensocial.googleusercontent.com/"
+ ],
+ "background_page": "background.html",
+ "options_page": "options.html",
+ "browser_action": {
+ "default_icon": "unauthorized.png",
+ "default_title": "Preview new waves",
+ "popup": "popup.html"
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/options.html b/chrome/common/extensions/docs/examples/extensions/wave/options.html
new file mode 100644
index 0000000..2639142
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/options.html
@@ -0,0 +1,86 @@
+<!--
+ Copyright 2010 Google
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+
+ Brian Kennish <byoogle@google.com>
+-->
+<title>Google Wave Notifier</title>
+<style>
+ body {
+ margin: 10px;
+ font: 84% Arial, sans-serif
+ }
+
+ h1 { font-size: 156% }
+
+ h1 img {
+ margin: 1px 5px 0 1px;
+ vertical-align: middle
+ }
+
+ h2 {
+ border-top: 1px solid #9cc2ef;
+ background-color: #ebeff9;
+ padding: 3px 5px;
+ font-size: 100%
+ }
+
+ dl { padding: 0 5px }
+
+ dt { font-weight: bold }
+
+ dd {
+ margin-top: 13px;
+ margin-bottom: 13px
+ }
+</style>
+<script>
+ onload = function() {
+ options.maxDigests.value = localStorage.maxDigests;
+ options.participantAnnotated.checked =
+ chrome.extension.getBackgroundPage().
+ parse(localStorage.participantAnnotated);
+
+ options.maxDigests.onchange = function() {
+ localStorage.maxDigests = this.value;
+ };
+
+ options.participantAnnotated.onchange = function() {
+ localStorage.participantAnnotated = this.checked;
+ };
+ };
+</script>
+<h1>
+ <img src="64.png" alt="Google Wave">
+ Google Wave Notifier
+</h1>
+<h2>Options</h2>
+<form id="options">
+ <dl>
+ <dt>Popup bubble:</dt>
+ <dd>
+ Show up to
+ <select name="maxDigests">
+ <option>5</option>
+ <option>10</option>
+ <option>20</option>
+ </select>
+ wave digests
+ </dd>
+ <dd>
+ <input type="checkbox" name="participantAnnotated" checked>
+ Show wave participants as tooltips
+ </dd>
+ </dl>
+</form>
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/popup.html b/chrome/common/extensions/docs/examples/extensions/wave/popup.html
new file mode 100644
index 0000000..a3d3f14
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/popup.html
@@ -0,0 +1,127 @@
+<!--
+ Copyright 2010 Google
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+
+ Brian Kennish <byoogle@google.com>
+-->
+<style>
+ body {
+ width: 400px;
+ font: small Arial, sans-serif;
+ color: initial
+ }
+
+ body > div {
+ border-bottom: 1px solid #edeff5;
+ padding: 3px 6px
+ }
+
+ .overflow { margin-right: 27px }
+
+ .digest, #logo, #authorization { cursor: pointer }
+
+ .digest { height: 64px }
+
+ .mouseover { background: #f4f6fc }
+
+ .title { font-weight: bold }
+
+ .snippet { color: #7f7f7f }
+
+ #authorization {
+ float: right;
+ margin-top: 14px;
+ text-decoration: underline;
+ color: #003ea8
+ }
+
+ #template { display: none }
+</style>
+<script>
+ const TABS = chrome.tabs;
+ const URL = 'https://wave.google.com/wave/';
+ const BACKGROUND = chrome.extension.getBackgroundPage();
+ const OAUTH = BACKGROUND.OAUTH;
+ const DIGESTS =
+ (BACKGROUND.parse(localStorage.digests) || []).
+ slice(0, localStorage.maxDigests);
+ const DIGEST_COUNT = DIGESTS.length;
+ var template;
+ var digest;
+
+ onload = function() {
+ document.getElementById('logo').onclick = function() {
+ TABS.create({url: URL});
+ };
+
+ const AUTHORIZATION = document.getElementById('authorization');
+ AUTHORIZATION.textContent = 'Sign ';
+
+ if (OAUTH.hasToken()) {
+ AUTHORIZATION.textContent += 'out';
+
+ AUTHORIZATION.onclick = function() {
+ BACKGROUND.clearInterval(BACKGROUND.id);
+ delete localStorage.digests;
+ OAUTH.clearTokens();
+ BACKGROUND.initialize();
+ BACKGROUND.BROWSER_ACTION.setIcon({path: 'unauthorized.png'});
+ BACKGROUND.load();
+ BACKGROUND.PRETTYLOAD.finish(BACKGROUND.UNKNOWN_COUNT);
+ close();
+ };
+ } else {
+ AUTHORIZATION.textContent += 'in';
+
+ AUTHORIZATION.onclick = function() { BACKGROUND.authorize(); };
+ }
+
+ const BODY = document.body;
+
+ for (var i = 0; i < DIGEST_COUNT; i++) {
+ template =
+ BODY.appendChild(document.getElementById('template').cloneNode(true));
+ digest = DIGESTS[i];
+ template.id = digest.waveId;
+
+ if (BACKGROUND.parse(localStorage.participantAnnotated)) {
+ template.title = digest.participants.join(', ');
+ }
+
+ template.getElementsByClassName('title')[0].textContent = digest.title;
+ template.getElementsByClassName('snippet')[0].textContent +=
+ digest.snippet;
+
+ template.onmouseover = function() { this.className += ' mouseover'; };
+
+ template.onmouseout = function() {
+ this.className = this.className.split(' ', 1);
+ };
+
+ template.onclick = function() {
+ TABS.create({url: URL + '#restored:wave:' + this.id});
+ };
+ }
+
+ if (BODY.scrollHeight > 600) { BODY.className = 'overflow'; }
+ };
+</script>
+<div>
+ <img id="logo" src="logo.png" alt="Google Wave">
+ <div id="authorization"></div>
+</div>
+<div class="digest" id="template">
+ <span class="title"></span>
+ <span class="snippet">&ndash; </span>
+</div>
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/prettyload.js b/chrome/common/extensions/docs/examples/extensions/wave/prettyload.js
new file mode 100644
index 0000000..4ae594d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/prettyload.js
@@ -0,0 +1,101 @@
+/*
+ Copyright 2010 Google
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+
+ Brian Kennish <byoogle@google.com>
+*/
+const ELLIPSIS = [' ', ' ', ' .', ' .', ' ..', ' ..', '...', '...'];
+const SPINNER = ['|', '/', '-', '\\'];
+const BOUNCING_BALL = [
+ ' 0', ' o ', ' _ ', ' o ', '0 ', ' o ', ' _ ', ' o '
+];
+const SINE_WAVE = ['.::.', '::..', ':..:', '..::'];
+const TRIANGLE_WAVE = ['/\\/\\', '\\/\\/'];
+
+function Prettyload(
+ frames,
+ backgroundColor,
+ successBackgroundColor,
+ timeoutBackgroundColor,
+ frameRate,
+ minDuration,
+ maxDuration
+) {
+ const BROWSER_ACTION = chrome.browserAction;
+ var startTime;
+ const MS_PER_SEC = 1000;
+ var id;
+ this.frames = frames !== undefined ? frames : ELLIPSIS;
+ this.backgroundColor = backgroundColor !== undefined ? backgroundColor : null;
+ this.successBackgroundColor =
+ successBackgroundColor !== undefined ? successBackgroundColor : null;
+ this.timeoutBackgroundColor =
+ timeoutBackgroundColor !== undefined ? timeoutBackgroundColor : null;
+ this.frameRate = frameRate !== undefined ? frameRate : 12;
+ this.minDuration = minDuration !== undefined ? minDuration : 1;
+ this.maxDuration = maxDuration !== undefined ? maxDuration : 10;
+
+ function finish(frame, backgroundColor) {
+ clearInterval(id);
+
+ if (backgroundColor) {
+ BROWSER_ACTION.setBadgeBackgroundColor({color: backgroundColor});
+ }
+
+ BROWSER_ACTION.setBadgeText({text: frame});
+ }
+
+ this.start = function(timeoutFrame) {
+ function timeout() {
+ finish(
+ timeoutFrame !== undefined ? timeoutFrame : '', TIMEOUT_BACKGROUND_COLOR
+ );
+ }
+
+ if (this.backgroundColor) {
+ BROWSER_ACTION.setBadgeBackgroundColor({color: this.backgroundColor});
+ }
+
+ startTime = Date.now();
+ const MAX_DURATION = this.maxDuration * MS_PER_SEC;
+ const FRAMES = this.frames;
+ var i = 0;
+ const FRAME_COUNT = FRAMES.length;
+ const TIMEOUT_BACKGROUND_COLOR = this.timeoutBackgroundColor;
+
+ if (0 < MAX_DURATION) {
+ BROWSER_ACTION.setBadgeText({text: FRAMES[i]});
+
+ id = setInterval(function() {
+ if (Date.now() - startTime < MAX_DURATION) {
+ BROWSER_ACTION.setBadgeText({text: FRAMES[++i % FRAME_COUNT]});
+ } else { timeout(); }
+ }, MS_PER_SEC / this.frameRate);
+ } else { timeout(); }
+
+ return this;
+ };
+
+ this.finish = function(successFrame) {
+ const SUCCESS_BACKGROUND_COLOR = this.successBackgroundColor;
+
+ setTimeout(function() {
+ finish(
+ successFrame !== undefined ? successFrame : '', SUCCESS_BACKGROUND_COLOR
+ );
+ }, this.minDuration * MS_PER_SEC - (Date.now() - startTime));
+
+ return this;
+ };
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/wave/unauthorized.png b/chrome/common/extensions/docs/examples/extensions/wave/unauthorized.png
new file mode 100644
index 0000000..dc8f763
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/wave/unauthorized.png
Binary files differ