conveyal/modeify

View on GitHub
client/session/index.js

Summary

Maintainability
B
6 hrs
Test Coverage
var model = require('component-model')
var moment = require('moment')

var analytics = require('../analytics')
const auth0 = require('../auth0')
var store = require('../browser-store')
var Commuter = require('../commuter')
var log = require('../log')('session')
var defaults = require('../components/segmentio/model-defaults/0.2.0')
var Plan = require('../plan')
var request = require('../request')
var User = require('../user')

/**
 * Deafult session settings
 */

var DEFAULT_SETTINGS = {}

/**
 * Session
 */

var Session = model('Session')
  .use(defaults({
    commuter: null,
    loaded: false,
    plan: null,
    settings: {},
    user: null,
    isLoggedIn: false
  }))
  .attr('commuter')
  .attr('loaded')
  .attr('plan')
  .attr('settings')
  .attr('user')
  .attr('isLoggedIn')
  .attr('serviceAlerts')

/**
 * Save settings on changes
 */

Session.on('change settings', function (session, settings) {
  store('session', settings)
})

/**
 * Logout
 */

Session.prototype.logout = function (next) {
  log('--> logging out')

  this.clear()

  next()
}

Session.prototype.clear = function () {
  store('auth0IdToken', null)
  store('commuter', null)
  store('plan', null)
  store('session', null)
  store('user', null)

  request.setAuthHeader(null)

  document.cookie = 'expires=Thu, 01 Jan 1970 00:00:00 UTC'

  session.set({
    commuter: null,
    isLoggedIn: false,
    loaded: false,
    plan: null,
    settings: {},
    user: null
  })

  this.isLoggedIn(false)
  this.emit('change email', null)
  this.emit('change places', [])
}

Session.prototype.isAdmin = function () {
  return this.isLoggedIn() && this.user().app_metadata()['isAdmin']
}

/**
 * Expose singleton `session`
 */

var session = window.session = module.exports = new Session()

/**
 * Touch.
 */

session.touch = function (ctx, next) {
  ctx.session = session
  if (session.loaded()) {
    next(null, session)
  } else {
    session.load(ctx, next)
  }
}

session.load = function (ctx, next) {
  session.settings(store('session') || DEFAULT_SETTINGS)

  loadUser(function (err) {
    if (err) return next(err)

    const user = session.user()

    if (user) {
      var userJson = user.toJSON()
      var registrationCode = store('registration-code')

      if (registrationCode) {
        userJson.registrationCode = registrationCode
        store('registration-code', null)
      }

      analytics.identify(user.user_id(), userJson)

      user.on('change', function () {
        store('user', user.toJSON())
      })
    } else {
      session.user(null)
      session.isLoggedIn(false)
    }

    loadCommuter(function (err, commuter) {
      if (err) return next(err)

      // store the commuter
      session.commuter(commuter)

      // load the plan
      var userOpts = (session.user() && session.user().user_metadata().modeify_opts)
        ? session.user().user_metadata().modeify_opts
        : {}
      session.plan(Plan.load(userOpts))

      // set the session as loaded
      session.loaded(true)

      // Store commuter changes
      commuter.on('change', function () {
        store('commuter', commuter.toJSON())

        if (!commuter.anonymous()) {
          commuter.save()
        }
      })

      // check for alerts
      loadServiceAlerts(function (err, alerts) {
        if (err) {
          console.log('error loading service alerts', err)
        }

        const today = moment()
        alerts = (alerts || []).filter(function (alert) {
          const fromDate = moment.utc(alert.fromDate)
          const toDate = moment.utc(alert.toDate)
          return !fromDate.isAfter(today, 'days') && !toDate.isBefore(today, 'days')
        })

        session.serviceAlerts(alerts)
      })

      next(null, session)
    })
  })
}

session.login = function (callback) {
  auth0.show(makeAuthResponseHandler(true, callback))
}

session.loginWithLink = function (ctx, next) {
  next()
}

/**
 * Log out
 */

session.logoutMiddleware = function (ctx, next) {
  log('logout %s', decodeURIComponent(ctx.path))

  session.logout(next)
}

session.signUp = function () {
  auth0.show({ initialScreen: 'signUp' }, makeAuthResponseHandler(true))
}

function makeAuthResponseHandler (alertIfFailed, callback) {
  return (authErr, authResult) => {
    const idToken = authResult ? authResult.idToken : null
    if (authErr || !idToken) {
      store('auth0IdToken', null)
      if (alertIfFailed) {
        window.alert('Failed to login')
      }
      if (typeof callback === 'function') {
        callback(authErr || new Error('Failed to obtain idToken'))
      }
      console.error(authErr || 'Failed to obtain idToken')
      return
    }

    store('auth0IdToken', idToken)
    request.setAuthHeader(idToken)

    auth0.getProfile(authResult.accessToken, (getProfileError, profile) => {
      if (getProfileError) {
        if (alertIfFailed) {
          window.alert('Failed to login')
        }
        if (typeof callback === 'function') {
          callback(getProfileError)
        }
        console.error(getProfileError)
        return
      }

      const user = new User(profile)

      // make sure new users have their createdAtUnix field set
      const userMetadata = user.user_metadata() || {}
      if (!userMetadata.createdAtUnix) {
        userMetadata.createdAtUnix = moment(profile.created_at).unix()
        user.user_metadata(userMetadata)
        user.saveUserMetadata(() => {})
      }

      session.user(user)
      session.isLoggedIn(true)
      session.emit('change email', session.user().email())
      session.emit('change places', session.user().user_metadata().modeify_places)

      store('user', user.toJSON())

      // update advancedSettings if present in user data
      // the plan might not be loaded when logging into manager app, so skip in that case
      if (profile.user_metadata && profile.user_metadata.modeify_opts && session.plan()) {
        const advancedSettings = [
          'bikeSpeed',
          'bikeTrafficStress',
          'carCostPerMile',
          'carParkingCost',
          'maxBikeTime',
          'maxWalkTime',
          'walkSpeed'
        ]

        advancedSettings.forEach((setting) => {
          const settingValue = profile.user_metadata.modeify_opts[setting]
          if (settingValue || settingValue === 0) {
            session.plan()[setting](settingValue)
          }
        })

        session.plan().store()
      }

      if (typeof callback === 'function') {
        callback()
      }
    })
  }
}

function loadUser (next) {
  if (session.user()) {
    // webapp is already initiated?
    return next(null, session.user())
  }

  const idToken = store('auth0IdToken')

  if (!idToken) {
    return next()
  }

  // initiate refresh of user data
  auth0.renewAuth(makeAuthResponseHandler(false, next))
}

function loadCommuter (next) {
  var commuterData = store('commuter')
  var user = session.user()

  if (session.commuter()) {
    next(null, session.commuter())
  } else if (commuterData) {
    if (user) {
      commuterData.anonymous = false
    }

    next(null, new Commuter(commuterData))
  } else if (session.isLoggedIn()) {
    request.get('/commuters', {
      account: user.getAccountId()
    }, function (err, res) {
      if (err || !res.body || res.body.length === 0) {
        next(null, new Commuter({
          account: user.getAccountId(),
          email: user.email(),
          givenName: 'unknown',
          surname: 'unknown',
          anonymous: false
        }))
      } else {
        next(null, new Commuter(res.body[0]))
      }
    })
  } else {
    next(null, new Commuter({
      anonymous: true
    }))
  }
}

function loadServiceAlerts (next) {
  request.get('/service-alerts', function (err, res) {
    if (err || !res.body || res.body.length === 0) {
      next(null, [])
    } else {
      next(null, res.body)
    }
  })
}