--- /dev/null
+fs = require 'fs'
+async = require 'async'
+af = require './api.coffee'
+
+token_file = "#{process.env.HOME}/.af-coffee-token"
+
+timeout = (ms, f) -> setTimeout f, ms
+
+# a session caches the api access token and prompts for the username and
+# password if it goes stale. It re-tries API calls that fail due to
+# invalid/expired token
+
+class Session
+ constructor: ->
+ @token = 0
+
+ api: (call, args..., callback) ->
+ async.waterfall [
+ (callback) =>
+ switch @token
+ when 0
+ get_token callback
+ when -1
+ login callback
+ else
+ callback null, @token
+ (token, callback) =>
+ @token = token
+ af[call] @token, args..., callback
+ ], (err, result) =>
+ # eg /app/xxx/stats sometimes returns 404 with wrong auth token
+ if err?.code is 403 or err?.code is 404
+ @token = -1
+ @api(call, args..., callback)
+ else
+ callback err, result
+
+ask = (opts, callback) ->
+ process.stdout.write opts.prompt
+ process.stdin.setEncoding 'utf8'
+ process.stdin.resume()
+ process.stdin.once 'data', (line) ->
+ if opts.silent
+ # send ^[[A^[[2K to move the cursor up one line, then clear that line
+ process.stdout.write new Buffer [27, 91, 65, 27, 91, 50, 75]
+ process.stdout.write opts.prompt + "***\n"
+ process.stdin.pause()
+ callback null, (line.substr 0, line.length - 1)
+
+get_token = (callback) ->
+ fs.readFile token_file, 'utf8', (err, token) ->
+ if err?
+ login callback
+ else
+ callback null, token
+
+login = (callback) ->
+ async.waterfall [
+ (callback) -> async.series [
+ (callback) -> ask prompt: 'username: ', callback
+ (callback) -> ask prompt: 'password: ', silent: true, callback
+ ], callback
+ ([username, password], callback) ->
+ af.login username, password, callback
+ (token, callback) ->
+ # wait for file write so there's no race condition if get_token gets called soon
+ fs.writeFile token_file, token, (err) ->
+ if err
+ console.log "Warning: couldn't cache auth token in #{token_file}: ", err
+ # don't pass on error, it's ok if we can't cache it
+ callback null, token
+ ], callback
+
+
+exports.new_session = ->
+ return new Session()