Created
December 4, 2012 16:04
-
-
Save killercup/4205541 to your computer and use it in GitHub Desktop.
Instapaper xAuth API for client side JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# # Instapaper API Class | |
# | |
# Instapaper xAuth API for client side JS. Depends on cross-domain requests. | |
# | |
# Reference: <http://www.instapaper.com/api/full> | |
# | |
# xAuth documentation from Twitter: <https://dev.twitter.com/docs/oauth/xauth> | |
# | |
# With help from <https://gist.github.com/447636> | |
# | |
# - - - | |
class Instapaper | |
# Always uses HTTPS | |
baseUrl: "https://www.instapaper.com/api/1/" | |
# Application keys for this application | |
consumer_key: 'SECRET' | |
consumer_secret: 'TOPSECRET' | |
# ## Class Methods | |
# ### Creates Nonce | |
# | |
# > "The oauth_nonce parameter is a unique token your application should | |
# generate for each unique request. Twitter will use this value to determine | |
# whether a request has been submitted multiple times." | |
# | |
# > <https://dev.twitter.com/docs/auth/authorizing-request> | |
generateNonce: -> | |
nonce = [] | |
length = 5 # arbitrary - looks like a good length | |
while length > 0 | |
nonce.push (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) | |
length-- | |
nonce.join "" | |
# UTC timestamp. | |
getUTCtimestamp: -> | |
(new Date((new Date).toUTCString())).getTime() / 1000 | |
# ### Creates 'header string' for 'Authorization' in HTTP header | |
# | |
# cf. <https://dev.twitter.com/docs/auth/authorizing-request> | |
authTemplate: (req) -> | |
auth = """OAuth oauth_consumer_key="#{fixedEncodeURIComponent req.consumer_key}", """ | |
if req.token? | |
auth += """oauth_token="#{fixedEncodeURIComponent req.token}", """ | |
auth += """oauth_signature_method="HMAC-SHA1", oauth_signature="#{fixedEncodeURIComponent req.signature}", oauth_timestamp="#{fixedEncodeURIComponent req.timestamp}", oauth_nonce="#{fixedEncodeURIComponent req.nonce}", oauth_version="1.0" | |
""".trim() | |
return auth | |
# ### Creates 'Signature base string' | |
# | |
# cf. <https://dev.twitter.com/docs/auth/creating-signature> | |
sigBaseTemplate: (req) -> | |
params = | |
oauth_consumer_key: req.consumer_key | |
oauth_nonce: req.nonce | |
oauth_signature_method: 'HMAC-SHA1' | |
oauth_timestamp: req.timestamp | |
oauth_version: '1.0' | |
if req.token? | |
params.oauth_token = req.token | |
if req.data? | |
params = $.extend params, req.data | |
# Params string: sort object by key, then make querystring | |
param_helper = [] | |
for i in Object.keys(params).sort() | |
param_helper.push "#{fixedEncodeURIComponent i}=#{fixedEncodeURIComponent params[i]}" | |
param_string = param_helper.join '&' | |
sig = "POST&#{ fixedEncodeURIComponent @baseUrl+req.url }&#{ fixedEncodeURIComponent param_string }" | |
return sig | |
# ## General Methods | |
makeSigningKey: -> | |
key = @consumer_secret + '&' | |
key += @token_secret if @token_secret? | |
return key | |
# ### Create Signature for `authTemplate()` | |
# | |
# Depends on HMAC-SHA1 from <http://caligatio.github.com/jsSHA/> | |
makeSignature: (req) -> | |
hmacGen = new jsSHA(@sigBaseTemplate(req), "TEXT") | |
hmacGen.getHMAC(@makeSigningKey(), "TEXT", "SHA-1", "B64") | |
# ### Creates `req`, an object with data specific for each request | |
makeRequestObject: (options) -> | |
req = $.extend { | |
consumer_key: @consumer_key | |
consumer_secret: @consumer_secret | |
nonce: @generateNonce() | |
timestamp: @getUTCtimestamp() | |
token: @token | |
token_secret: @token_secret | |
method: 'POST' | |
}, options | |
# Add signature, depends on req data so far | |
req.signature = @makeSignature(req) | |
req | |
# Creates new Ajax request | |
# | |
# Always uses POST | |
request: (options) -> | |
req = options.req ||= @makeRequestObject(url: options.url, data: options.data) | |
auth = @authTemplate(options.req) | |
$.ajax | |
url: "#{@baseUrl}#{options.url}", | |
dataType: do -> options.dataType || "json" | |
type: 'POST' | |
data: options.data | |
headers: | |
Authorization: auth | |
# ## Specific API Methods | |
# ### Gets an OAuth access token for a user. | |
# | |
# * Requires username and password | |
# * Also needs HTTPS | |
requestToken: (user, password) -> | |
unless user? and password? | |
throw 'Please provide username and password.' | |
@user = user | |
url = "oauth/access_token" | |
data = | |
x_auth_username: user | |
x_auth_password: password | |
x_auth_mode: "client_auth" | |
# Make Ajax request | |
tokening = @request | |
url: url | |
req: @makeRequestObject(url: url, data: data) | |
data: data | |
dataType: 'text' | |
# Sucessful response looks like: | |
# | |
# `oauth_token=aabbccdd&oauth_token_secret=efgh1234` | |
tokening.done (response) => | |
# Uses `jline2object` (see below) to retrieve data from query string | |
data = qline2object(response) | |
# Save oauth tokens to instance variables | |
@token = data.oauth_token | |
@token_secret = data.oauth_token_secret | |
# Please can for a failure case yourself! | |
# | |
# `tokening.fail (jqXHR, textStatus, errorThrown) => ...` | |
return tokening | |
# ### Returns the currently logged in user. | |
# | |
# `[{"type":"user","user_id":54321,"username":"TestUserOMGLOL"}]` | |
verifyCredentials: -> | |
@request | |
url: "account/verify_credentials" | |
# Example method | |
# | |
# I won't add all the API stuff here, just do something like: | |
# | |
# insta = new Instapaper() | |
# insta.requestToken(username, password) | |
# insta.request(url: "bookmarks/list") | |
bookmarkList: -> | |
@request | |
url: "bookmarks/list" | |
module?.exports = Instapaper | |
# - - - | |
# Helper function to transform querystring/qline to JS object | |
# | |
# Insanely fast: <http://jsperf.com/query-str-parsing-regex-vs-split/5> | |
qline2object = (query="") -> | |
result = {} | |
parts = query.split("&") | |
for item in parts | |
item = item.split("=") | |
result[item[0]] = item[1] | |
result | |
# native encodeURIComponent isn't sufficient here | |
# | |
# from <https://gist.github.com/447636> | |
fixedEncodeURIComponent = (str) -> | |
encodeURIComponent(str) | |
.replace(/!/g, '%21') | |
.replace(/'/g, '%27') | |
.replace(/\(/g, '%28') | |
.replace(/\)/g, '%29') | |
.replace(/\*/g, '%2A') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this. Saved a few hours of digging around to figure out the exact implementation used by I.
Cheers!