ducky/web

Paddy 2015-06-30 Parent:3bdc03963abe Child:9b97c908a706

14:275a83e4c02e Go to Latest

ducky/web/src/helpers/oauth-refresh.js

Persist session data to localStorage. Create a helper library that figures out whether to write to chrome.storage.local or window.localStorage, and unifies their two APIs. Update the Me model to use the getOrFetch method for the profiles collection when retrieving the user's profile. This, unfortunately, makes it an async call (because we may need to fetch data from the server), so we can no longer have it be a derived property, which is a shame. It instead must just be the me.profile() function. Separate out the logic to determine when an access token expires, and turn it into the tokenExpires function. Fill the writeToCache placeholder with the logic to store the current session in either window.localStorage or chrome.storage.local, whichever is the more appropriate, using the helper library. Create the load helper function that will attempt to read session data from localStorage or chrome.storage.local, whichever the library decides is available, and updates the session based on it. Implement the logout function, which just uses the helper library to remove the session data from window.localStorage or chrome.storage.local. We should also be resetting the app.me variable, however. Create a debouncedWriteToCache function that will only write to the cache once every 250 ms, to avoid rushes on the cache. When instantiating our app.me variable, load it in from localStorage or chrome.storage.local if we can. Also, listen for changes to app.me, and persist them to chrome.storage.local or localStorage.

History
paddy@11 1 import app from 'ampersand-app'
paddy@11 2 import xhr from 'xhr'
paddy@11 3 import qs from 'qs'
paddy@11 4 import Sync from 'ampersand-sync'
paddy@11 5 import config from '../config'
paddy@11 6
paddy@11 7 const getRefresh = (opts, callback) => {
paddy@11 8 const refreshOpts = {
paddy@11 9 url: config.urlBase + '/token',
paddy@11 10 method: 'POST',
paddy@11 11 headers: {
paddy@11 12 'Content-Type': 'application/x-www-form-urlencoded',
paddy@11 13 'Authorization': 'Basic ' + btoa(config.clientID + ':' + config.clientSecret),
paddy@11 14 },
paddy@11 15 data: qs.stringify({
paddy@11 16 'grant_type': 'refresh_token',
paddy@11 17 'refresh_token': app.me.refresh_token
paddy@11 18 }),
paddy@11 19 }
paddy@11 20 xhr(refreshOpts, function(err, resp, body) {
paddy@11 21 if (resp.statusCode != 200) {
paddy@11 22 callback(err, resp, body)
paddy@11 23 return
paddy@11 24 }
paddy@11 25 if (body && resp.headers['content-type'] == 'application/json') {
paddy@11 26 try {
paddy@11 27 body = JSON.parse(body)
paddy@11 28 } catch (err) {
paddy@11 29 app.trigger('token:refreshError', body)
paddy@11 30 callback(err, resp, body)
paddy@11 31 }
paddy@11 32 }
paddy@11 33 if (body.access_token) {
paddy@11 34 app.me.set(body)
paddy@11 35 } else {
paddy@11 36 app.trigger('token:refreshError', body)
paddy@11 37 }
paddy@11 38 callback(err, resp, body)
paddy@11 39 })
paddy@11 40 }
paddy@11 41
paddy@11 42 const shouldRefreshXHR = (err, resp, body) => {
paddy@11 43 if(body && resp.headers['Content-Type'] == 'application/json') {
paddy@11 44 try {
paddy@11 45 body = JSON.parse(body)
paddy@11 46 } catch (err) {
paddy@11 47 return false
paddy@11 48 }
paddy@11 49 }
paddy@11 50 if (resp.statusCode == 401 && body.errors && body.errors[0]
paddy@11 51 && body.errors[0].header == 'authorization' && body.errors[0].error == 'access_denied') {
paddy@11 52 return true
paddy@11 53 }
paddy@11 54 return false
paddy@11 55 }
paddy@11 56
paddy@11 57 const doRefreshXHR = (opts, callback) => {
paddy@11 58 opts.noRefresh = true
paddy@11 59 getRefresh(opts, function(err, resp, body) {
paddy@11 60 if (body.access_token) {
paddy@11 61 opts.headers['Authorization'] = 'Bearer '+body.access_token
paddy@11 62 xhr(opts, callback)
paddy@11 63 }
paddy@11 64 })
paddy@11 65 }
paddy@11 66
paddy@11 67 const refreshXHR = (opts, callback) => {
paddy@11 68 return xhr(opts, function(err, resp, body) {
paddy@11 69 if (opts.noRefresh || !shouldRefreshXHR(err, resp, body)) {
paddy@11 70 callback(err, resp, body)
paddy@11 71 return
paddy@11 72 }
paddy@11 73 doRefreshXHR(opts, callback)
paddy@11 74 })
paddy@11 75 xhr(opts, callback)
paddy@11 76 }
paddy@11 77
paddy@11 78 const shouldRefreshSync = (resp) => {
paddy@11 79 if (resp && resp.errors && resp.errors[0]
paddy@11 80 && resp.errors[0].header == 'authorization' && resp.errors[0].error == 'access_denied') {
paddy@11 81 return true
paddy@11 82 }
paddy@11 83 return false
paddy@11 84 }
paddy@11 85
paddy@11 86 const doRefreshSync = (action, moc, opts) => {
paddy@11 87 opts.noRefresh = true
paddy@11 88 getRefresh(opts, function(err, resp, body) {
paddy@11 89 if (body.access_token) {
paddy@11 90 Sync(action, moc, opts)
paddy@11 91 }
paddy@11 92 })
paddy@11 93 }
paddy@11 94
paddy@11 95 const refreshSync = (action, moc, opts) => {
paddy@11 96 const oldError = opts.error
paddy@11 97 opts.error = function(resp) {
paddy@11 98 if(opts.noRefresh || !shouldRefreshSync) {
paddy@11 99 oldError(resp)
paddy@11 100 return
paddy@11 101 }
paddy@11 102 doRefreshSync(action, moc, opts)
paddy@11 103 }
paddy@11 104 Sync(action, moc, opts)
paddy@11 105 }
paddy@11 106
paddy@11 107 const refresh = {
paddy@11 108 Sync: refreshSync,
paddy@11 109 XHR: refreshXHR,
paddy@11 110 }
paddy@11 111
paddy@11 112 export default refresh