Created
July 29, 2014 07:03
-
-
Save LeonardoGentile/66f41ffbfe1246ab406e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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