'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 = ->
]
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) ->
# 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
$.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.
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'
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
# 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'
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
# 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
# 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
_.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
apiPatronInfo: ->
ok = (x) ->
- update_session_cache x
+ session.links.update x
od.$.triggerHandler 'od.patroninfo', x
return
.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 ''}"
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 ''}"
--- /dev/null
+# 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