From: Steven Chan Date: Fri, 29 Aug 2014 18:47:59 +0000 (-0700) Subject: Use Session class to implement a session cache X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=bf67304d579a7e74e01025d7f1519f58dc378fdc;p=contrib%2Foverdrive-eg-opac.git Use Session class to implement a session cache Signed-off-by: Steven Chan --- diff --git a/src/od_api.coffee b/src/od_api.coffee index 59ebf6a..56cb067 100644 --- a/src/od_api.coffee +++ b/src/od_api.coffee @@ -5,7 +5,8 @@ define [ 'cookies' 'moment' 'od_config' -], ($, _, json, C, M, config) -> + 'od_session' +], ($, _, json, C, M, config, Session) -> # Dump the given arguments or log them to console log = -> @@ -49,105 +50,17 @@ define [ ] eventObject = $({}).on eventList.join(' '), (e, x, y...) -> log e.namespace, x, y - # We require the service of a session object to store essentials bits of - # information during a login session and between page reloads. Here, we - # define the default version of the session, which will be used if no - # session is active. - default_session = - - prefs: {} - - credentials: {} - - # An essential role of the session object is to store the response - # parameters that are provided as a result of authenticating the client - # or the patron. Two components, the token type and the token, are - # computed into an Authorization header so that they can be easily - # submitted to an ajax call. - token: {} - #parameters: '' - #headers: Authorization: '' - - # Another role is to store the endpoints of the various APIs. These - # include the endpoints for authenticating the client or the patron and - # the endpoints for getting library or patron information. Upon - # authentication, other endpoints are dynamically accumulated within - # the session object. - links: - token: href: '//oauth.overdrive.com/token' - libraries: href: "//api.overdrive.com/v1/libraries/#{config.accountID}" - patrontoken: href: '//oauth-patron.overdrive.com/patrontoken' - patrons: href: '//patron.api.overdrive.com/v1/patrons/me' - holds: href: '' - checkouts: href: '' - products: '' - advantageAccounts: '' - search: '' - availability: '' - - # Another role is to preserve the mapping between format id and format - # name that will be provided by the Library Account API. - labels: {} - + # On page load, we unserialize the text string found in local storage into + # an object, or if there is no string yet, we create the default object. # The session object uses a local storage mechanism based on window.name; # see # http://stackoverflow.com/questions/2035075/using-window-name-as-a-local-data-cache-in-web-browsers # for pros and cons and alternatives. - # - # On page load, we unserialize the text string found in local storage into - # an object, or if there is no string yet, we create the default object. - session = - try - json.parse window.name - catch - default_session - - # On window unload, we timestamp the current session object and serialize it - # into local storage so that it survives page reloads. - $(window).on 'unload', -> - session.now = M().toISOString() - window.name = json.stringify session - - # Use a cheap, nasty way to enforce a sanity constraint: session link templates should have empty - # values unless the current session is logged in. - $.extend session.links, { search: '', availability: '' } unless Boolean C('eg_loggedin') or window.IAMXUL - - # Return a new object from given an object that has a 'key' property and a - # 'value' property - to_object = (from, key, value) -> - to = {} - to[x[key]] = x[value] for x in from - return to + session = new Session window.name, Boolean C('eg_loggedin') or window.IAMXUL - # Update library or patron account information for the session - update_session_cache = (x) -> - - # Cache any links - $.extend session.links, x.links if x.links - - # Cache any linkTemplates; these are sourced only via the Patron Information API - $.extend session.links, x.linkTemplates if x.linkTemplates - - #$.extend session, x - - # Cache any mapping between format names and format IDs - $.extend session.labels, to_object x.formats, 'id', 'name' if x.formats - - return x - - # Revert the session cache to its default version - expire_session_cache = -> - $.extend session, default_session - - # Define a function to check if the session object contains a patron access - # token. It is enough to test if the parameters.scope text string mentions - # the word 'patron'. - #is_patron_access_token = -> /patron/i.test session.token.parameters?.scope - is_patron_access_token = -> /patron/i.test session.token?.scope - - # Calculate a string encompassing the token type and access token of a - # given response object to an Overdrive login request - token_header = (x) -> Authorization: "#{x.token_type} #{x.access_token}" + # On window unload, we serialize it into local storage so that it survives + # page reloads. + $(window).on 'unload', -> window.name = session.store() # Customize the plain jQuery ajax to post a request for an access token _api = (url, data) -> @@ -233,20 +146,15 @@ define [ # Notification that there are possible changes of values from # preferences page that should be updated in the session cache - 'od.prefs': (ev, x) -> $.extend session.prefs, x + 'od.prefs': (ev, x) -> session.prefs.update x # Expire patron access token if user is no longer logged into EG 'od.logout': (ev, x) -> if x is 'eg' - expire_session_cache if is_patron_access_token() + session = new Session() if session.token.is_patron_access() log: log - # It's necessary to expose the session cache to other modules because - # they may need to access certain stored values, in particular, the - # place hold page needs to get access to the email address - session: session - proxy: proxy # Map format id to format name using current session object @@ -270,7 +178,7 @@ define [ $.ajax $.extend {}, # The current Authorization string is always added to the HTTP header. - headers: session.token.headers + headers: Authorization: "#{session.token.token_type} #{session.token.access_token}" # The URL endpoint is converted to its reverse proxy version, because # we are using the Evergreen server as a reverse proxy to the Overdrive # server. @@ -320,11 +228,9 @@ define [ apiDiscAccess: -> ok = (x) -> - # Cache the server's response object as a general reference point - #session.token.parameters = x - session.token = x - # Cache the access token so that it can be used in future api calls - session.token.headers = token_header x + # Cache the server's response object and publish it to other + # modules + session.token.update x od.$.triggerHandler 'od.clientaccess', x _api session.links.token.href, grant_type: 'client_credentials' @@ -349,7 +255,8 @@ define [ get = -> od.api session.links.libraries.href ok = (x) -> - update_session_cache x + session.links.update x + session.labels.update x od.$.triggerHandler 'od.libraryaccount', x return @@ -358,7 +265,7 @@ define [ # Retry if we got a 401 error code if jqXHR.status is 401 - if is_patron_access_token() + if session.token.is_patron_access() # Current OD patron access token may have expired od.$.triggerHandler 'od.logout', 'od' @@ -390,19 +297,17 @@ define [ login: (credentials) -> # Temporarily store the username and password from the login form - # into the session cache, and invalidate the session cache so that + # into the session cache, and invalidate the session token so that # the final part of login sequence can complete. if credentials - $.extend session.credentials, credentials - #delete session.token.parameters - session.token = {} + session.creds.update credentials + session.token.update() od.$.triggerHandler 'od.login' return - # Return a promise to a resolved deferredment if session cache is still valid + # Return a promise to a resolved deferredment if session token is still valid # TODO is true if in staff client but shouldn't be - #if session.token.parameters - if is_patron_access_token() + if session.token.is_patron_access() return $.Deferred().resolve().promise() # Request OD service for a patron access token using credentials @@ -417,23 +322,22 @@ define [ # Retrieve values from preferences page and save them in the # session cache for later reference - $.extend( session.prefs, + session.prefs.update barcode: x 'barcode' email_address: x 'email address' home_library: x 'home library' - ) # Use barcode as username or the username that was stored in # session cache (in the hope that is a barcode) or give up with # a null string - un = session.prefs.barcode or session.credentials?.username or '' + un = session.prefs.barcode or session.creds.un() # Use the password that was stored in session cache or a dummy value - pw = if config.password_required is 'false' then 'xxxx' else session.credentials?.password or 'xxxx' + pw = session.creds.pw config.password_required # Remove the stored credentials from cache as soon as they are # no longer needed - session.credentials = {} + session.creds.update() # Determine the Open Auth scope by mapping the long name of EG # home library to OD authorization name @@ -449,9 +353,7 @@ define [ # Complete login sequence if the session cache is invalid ok = (x) -> - #session.token.parameters = x - session.token = x - session.token.headers = token_header x + session.token.update x od.$.triggerHandler 'od.patronaccess', x # Get patron preferences page @@ -512,7 +414,7 @@ define [ _.where(y.actions.hold.fields, name: 'reserveId')[0].value = y.id # We jam the email address from the prefs page into the fields object from the server # so that the new form will display it. - if email_address = od.session.prefs.email_address + if email_address = session.prefs.email_address _.where(y.actions.hold.fields, name: 'emailAddress')[0].value = email_address od.$.triggerHandler 'od.availability', y @@ -522,7 +424,7 @@ define [ apiPatronInfo: -> ok = (x) -> - update_session_cache x + session.links.update x od.$.triggerHandler 'od.patroninfo', x return @@ -530,7 +432,7 @@ define [ .then ok, logError apiHoldsGet: (x) -> - return unless is_patron_access_token() + return unless session.token.is_patron_access() od.api "#{session.links.holds.href}#{if x?.productID then x.productID else ''}" @@ -569,7 +471,7 @@ define [ arguments apiCheckoutsGet: (x) -> - return unless is_patron_access_token() + return unless session.token.is_patron_access() od.api "#{session.links.checkouts.href}#{if x?.reserveID then x.reserveID else ''}" diff --git a/src/od_session.coffee b/src/od_session.coffee new file mode 100644 index 0000000..1334bd3 --- /dev/null +++ b/src/od_session.coffee @@ -0,0 +1,131 @@ +# We require the service of a session object to store essentials bits of +# information during a login session and between page reloads. Here, we +# define a Session class implementing the service. + +define [ + 'json' + 'lodash' + 'od_config' +], ( + json + _ + config +) -> + + # A base class defining utilitarian methods + class U + update: (x) -> + return unless x + t = @ + t extends x + return + store: -> + json.stringify @, ( (n, v) -> if n is 'prototype' then undefined else v ), ' ' + retrieve: (x) -> + x = if _.isString x + try + json.parse x + catch + undefined + + class Prefs extends U + @default: + barcode: '' + email_address: '' + home_library: '' + constructor: (x) -> @update x + update: (x) -> super x or Prefs.default + + class Creds extends U + @default: + username: '' + password: 'xxxx' + constructor: (x) -> @update x + update: (x) -> super x or Creds.default + + # Calculate the effective username: either a barcode or a username (in the + # hope that is a barcode) stored in session cache, or default to a null + # string + un: -> @barcode or @username + + # Calculate the effective password: either a password stored in session + # cache or a dummy value + pw: (required) -> if required then @password else 'xxxx' + + # An essential role of the session object is to store the properties that + # are provided as a result of authenticating the client or the patron. + class Token extends U + @default: + access_token: undefined + expires_in: undefined + scope: undefined + token_type: undefined + + constructor: (x) -> @update x + update: (x) -> super x or Token.default + # Is there a patron access token? It is enough to test if the + # parameters.scope text string mentions the word 'patron'. + is_patron_access: -> /patron/i.test @scope + + # Store the endpoints of the various APIs. These include the endpoints for + # authenticating the client or the patron and the endpoints for getting + # library or patron information. Upon authentication, other endpoints are + # dynamically accumulated within the object. + class Links extends U + @default: + token: href: '//oauth.overdrive.com/token' + libraries: href: "//api.overdrive.com/v1/libraries/#{config.accountID}" + patrontoken: href: '//oauth-patron.overdrive.com/patrontoken' + patrons: href: '//patron.api.overdrive.com/v1/patrons/me' + holds: href: '' + checkouts: href: '' + products: '' + advantageAccounts: '' + search: '' + availability: '' + constructor: (x, logged_in) -> + @update x + @calibrate logged_in if x + return + update: (x) -> + if x is undefined + super Links.default + else + super x.links if x.links + super x.linkTemplates if x.linkTemplates + return + + # Link templates should have empty values unless the current session is + # logged in. + calibrate: (logged_in) -> + @search = @availability = '' unless logged_in + return @ + + # Preserve the mapping between format id and format name that will be + # provided by the Library Account API. + class Labels extends U + constructor: (x) -> @update x + update: (x) -> super @to_object x.formats, 'id', 'name' if x?.formats + # Return a new object from given an object that has a 'key' property and a + # 'value' property + to_object: (from, key, value) -> + to = {} + if from?.length > 0 + to[x[key]] = x[value] for x in from + return to + + # Define a session object as a collection of sub-objects of the types just + # defined. Property values of any sub-object can be given in the argument. + # The argument can be a JSON string or an object. If there are no property + # values given for a sub-object, intrinsic values will be used. + class Session extends U + constructor: (x, logged_in) -> + x = @retrieve x + @prefs = new Prefs x?.prefs + @creds = new Creds x?.creds + @token = new Token x?.token + @links = new Links x, logged_in + @labels= new Labels x + return + + return Session