Skip to content

Instantly share code, notes, and snippets.

@LeonardoGentile
Created July 29, 2014 07:03
Show Gist options
  • Save LeonardoGentile/66f41ffbfe1246ab406e to your computer and use it in GitHub Desktop.
Save LeonardoGentile/66f41ffbfe1246ab406e to your computer and use it in GitHub Desktop.
'use strict'
###*
# @ngdoc service
# @name myApp.loginService
# @description
# # loginService
###
angular.module('myApp', [])
.provider('loginService', () ->
userToken = localStorage.getItem("userToken")
errorState = "app.error"
logoutState = "app.home"
@$get = ($rootScope, $http, $q, $state) ->
###
Low-level, private functions.
###
setHeaders = (token) ->
if !token
delete $http.defaults.headers.common["X-Token"]
return
$http.defaults.headers.common["X-Token"] = token.toString()
return
setToken = (token) ->
if !token
localStorage.removeItem("userToken")
else
localStorage.setItem("userToken", token)
setHeaders(token)
return
getLoginData = ->
if userToken
setHeaders(userToken)
else
wrappedService.userRole = userRoles.public
wrappedService.isLogged = false
wrappedService.doneLoading = true
return
managePermissions = () ->
# Register routing function.
$rootScope.$on "$stateChangeStart", (event, to, toParams, from, fromParams) ->
###
$stateChangeStart is a synchronous check to the accessLevels property
if it's not set, it will setup a pendingStateChange and will let
the grandfather resolve do his job.
In short:
If accessLevels is still undefined, it let the user change the state.
Grandfather.resolve will either let the user in or reject the promise later!
###
if wrappedService.userRole is null
wrappedService.doneLoading = false
wrappedService.pendingStateChange = {
to: to,
toParams: toParams
}
return
# if the state has undefined accessLevel, anyone can access it.
# NOTE: if `wrappedService.userRole === undefined` means the service still doesn't know the user role,
# we need to rely on grandfather resolve, so we let the stateChange success, for now.
if to.accessLevel is `undefined` or to.accessLevel.bitMask & wrappedService.userRole.bitMask
angular.noop() # requested state can be transitioned to.
else
event.preventDefault()
$rootScope.$emit "$statePermissionError"
$state.go(
errorState,
{error: "unauthorized"},
{location: false, inherit: false}
)
return
###
Gets triggered when a resolve isn't fulfilled
NOTE: when the user doesn't have required permissions for a state, this event
it's not triggered.
In order to redirect to the desired state, the $http status code gets parsed.
If it's an HTTP code (ex: 403), could be prefixed with a string (ex: resolvename403),
to handle same status codes for different resolve(s).
This is defined inside $state.redirectMap.
###
$rootScope.$on "$stateChangeError", (event, to, toParams, from, fromParams, error) ->
###
This is a very clever way to implement failure redirection.
You can use the value of redirectMap, based on the value of the rejection
So you can setup DIFFERENT redirections based on different promise errors.
###
errorObj = undefined
redirectObj = undefined
# in case the promise given to resolve function is an $http request
# the error is a object containing the error and additional informations
error = (if (typeof error is "object") then error.status.toString() else error)
# in case of a random 4xx/5xx status code from server, user gets loggedout
# otherwise it *might* forever loop (look call diagram)
wrappedService.logoutUser() if /^[45]\d{2}$/.test(error)
###
Generic redirect handling.
If a state transition has been prevented and it's not one of the 2 above errors, means it's a
custom error in your application.
redirectMap should be defined in the $state(s) that can generate transition errors.
###
if angular.isDefined(to.redirectMap) and angular.isDefined(to.redirectMap[error])
if typeof to.redirectMap[error] is "string"
return $state.go(
to.redirectMap[error],
{ error: error },
{ location: false, inherit: false }
)
else if typeof to.redirectMap[error] is "object"
redirectObj = to.redirectMap[error]
return $state.go(
redirectObj.state,
{ error: redirectObj.prefix + error }
{ location: false, inherit: false }
)
return $state.go(
errorState,
{ error: error },
{ location: false, inherit: false }
)
return
###
High level, public methods
###
wrappedService = {
loginHandler: (user, status, headers, config) ->
###
Custom logic to manually set userRole goes here
Commented example shows an userObj coming with a 'completed'
property defining if the user has completed his registration process,
validating his/her email or not.
EXAMPLE:
if (user.hasValidatedEmail) {
wrappedService.userRole = userRoles.registered;
} else {
wrappedService.userRole = userRoles.invalidEmail;
$state.go('app.nagscreen');
}
###
# setup token
setToken user.token
# update user
angular.extend wrappedService.user, user
# flag true on isLogged
wrappedService.isLogged = true
# update userRole
wrappedService.userRole = user.userRole
user
loginUser: (httpPromise) ->
httpPromise.success @loginHandler
return
logoutUser: (httpPromise) ->
###
De-registers the userToken remotely
then clears the loginService as it was on startup
###
setToken null
@userRole = userRoles.public
@user = {}
@isLogged = false
$state.go logoutState
return
resolvePendingState: (httpPromise) ->
checkUser = $q.defer()
self = this
pendingState = self.pendingStateChange
# When the $http is done, we register the http result into loginHandler, `data` parameter goes into loginService.loginHandler
httpPromise.success self.loginHandler
httpPromise.then (success = (httpObj) ->
self.doneLoading = true
# duplicated logic from $stateChangeStart, slightly different, now we surely have the userRole informations.
if pendingState.to.accessLevel is `undefined` or pendingState.to.accessLevel.bitMask & self.userRole.bitMask
checkUser.resolve()
else
checkUser.reject "unauthorized"
return
), reject = (httpObj) ->
checkUser.reject httpObj.status.toString()
return
###
I setted up the state change inside the promises success/error,
so i can safely assign pendingStateChange back to null.
###
self.pendingStateChange = null
checkUser.promise
###
Public properties
###
userRole: null
user: {}
isLogged: null
pendingStateChange: null
doneLoading: null
}
getLoginData()
managePermissions()
wrappedService
return
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment