Skip to content

Instantly share code, notes, and snippets.

@killercup
Created December 4, 2012 16:04
Show Gist options
  • Save killercup/4205541 to your computer and use it in GitHub Desktop.
Save killercup/4205541 to your computer and use it in GitHub Desktop.
Instapaper xAuth API for client side JS
# # 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')
@adityabhaskar
Copy link

Thanks for this. Saved a few hours of digging around to figure out the exact implementation used by I.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment