diff options
author | kathyw@chromium.org <kathyw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-18 14:56:01 +0000 |
---|---|---|
committer | kathyw@chromium.org <kathyw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-18 14:56:01 +0000 |
commit | 1608605cc7d97ac18a2927cdb63e6d247515a585 (patch) | |
tree | 0e109d51b164c024086325aaf6fd9a6c2fec80dc /chrome/common | |
parent | 502f7fe3210f4a80ffbc4d223f20fb89b316615d (diff) | |
download | chromium_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')
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 Binary files differnew file mode 100644 index 0000000..cec8dae --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/128.png diff --git a/chrome/common/extensions/docs/examples/extensions/wave/16.png b/chrome/common/extensions/docs/examples/extensions/wave/16.png Binary files differnew file mode 100644 index 0000000..80a6968 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/16.png diff --git a/chrome/common/extensions/docs/examples/extensions/wave/48.png b/chrome/common/extensions/docs/examples/extensions/wave/48.png Binary files differnew file mode 100644 index 0000000..06c8adc --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/48.png diff --git a/chrome/common/extensions/docs/examples/extensions/wave/64.png b/chrome/common/extensions/docs/examples/extensions/wave/64.png Binary files differnew file mode 100644 index 0000000..2090cfb --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/64.png diff --git a/chrome/common/extensions/docs/examples/extensions/wave/authorized.png b/chrome/common/extensions/docs/examples/extensions/wave/authorized.png Binary files differnew file mode 100644 index 0000000..80a6968 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/authorized.png 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 Binary files differnew file mode 100644 index 0000000..6593b01 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/logo.png 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">– </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 Binary files differnew file mode 100644 index 0000000..dc8f763 --- /dev/null +++ b/chrome/common/extensions/docs/examples/extensions/wave/unauthorized.png |