You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
spa_instat_tv/src/features/AuthStore/hooks/useAuth.tsx

317 lines
7.8 KiB

import {
useCallback,
useState,
useMemo,
useEffect,
} from 'react'
import { useHistory } from 'react-router'
import type { User } from 'oidc-client'
import isString from 'lodash/isString'
import isBoolean from 'lodash/isBoolean'
import { PAGES } from 'config'
import {
addLanguageUrlParam,
writeToken,
removeToken,
readToken,
setCookie,
removeCookie,
isMatchPage,
TOKEN_KEY,
} from 'helpers'
import {
useLocalStore,
useSessionStore,
useToggle,
useEventListener,
} from 'hooks'
import { useLexicsStore } from 'features/LexicsStore'
import { queryParamStorage } from 'features/QueryParamsStorage'
import type { UserInfo, FailedResponse } from 'requests'
import {
getUserInfo,
checkDevice,
getTokenVirtualUser,
} from 'requests'
import { userManager } from '../config'
import { needCheckNewDeviсe } from '../helpers'
export const useAuth = () => {
const { changeLang, lang } = useLexicsStore()
const history = useHistory()
const {
close: markUserLoaded,
isOpen: loadingUser,
} = useToggle(true)
const [user, setUser] = useState<User>()
const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false)
const [userInfo, setUserInfo] = useState<UserInfo>()
const login = useCallback(async () => {
userManager.signinRedirect({ extraQueryParams: { lang } })
}, [lang])
const logout = useCallback((key?: string) => {
setPage(history.location.pathname)
userManager.clearStaleState()
userManager.createSigninRequest().then(({ url }) => {
const urlWithLang = addLanguageUrlParam(lang, url)
userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
})
removeToken()
if (key !== 'saveToken') {
removeCookie('access_token')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lang])
const storeUser = useCallback((loadedUser: User) => {
setUser(loadedUser)
writeToken(loadedUser.access_token)
setCookie({
exdays: 1,
name: 'access_token',
value: loadedUser.access_token,
})
}, [])
const checkUser = useCallback(async () => {
const loadedUser = await userManager.getUser()
if (!loadedUser) {
if (!readToken()) {
const token = await getTemporaryToken()
token && await fetchUserInfo()
return Promise.resolve()
}
return Promise.reject()
}
storeUser(loadedUser)
markUserLoaded()
return loadedUser
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
storeUser,
markUserLoaded,
])
const [page, setPage] = useLocalStore({
clearOnUnmount: true,
defaultValue: '',
key: 'matchBackLocation',
validator: isString,
})
const [search, setSearch] = useLocalStore({
clearOnUnmount: true,
defaultValue: '',
key: 'searchBack',
validator: isString,
})
const [isFromLanding, setIsFromLanding] = useSessionStore({
clearOnUnmount: true,
defaultValue: false,
key: 'isFromLanding',
validator: isBoolean,
})
useEffect(() => {
if (isMatchPage()) setPage(history.location.pathname)
if (history.location.pathname !== page) setIsFromLanding(false)
}, [
history.location.pathname,
page,
setIsFromLanding,
setPage,
])
const getTemporaryToken = async () => {
try {
const { access_token } = await getTokenVirtualUser()
writeToken(access_token)
setCookie({
exdays: 1,
name: 'access_token',
value: access_token,
})
return access_token
// eslint-disable-next-line no-empty
} catch {
return ''
}
}
const signinRedirectCallback = useCallback(() => {
userManager.signinRedirectCallback()
.then((loadedUser) => {
storeUser(loadedUser)
queryParamStorage.clear()
if (page.includes(PAGES.useraccount)) {
history.push(PAGES.home)
} else {
const route = `${page}${search}`
history.push(route)
}
markUserLoaded()
setPage('')
setSearch('')
// }
}).catch(login)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
login,
storeUser,
markUserLoaded,
])
useEventListener({
callback: useCallback(async (e: StorageEvent) => {
const loadedUser = await userManager.getUser()
if (
e.storageArea !== localStorage
|| e.key !== TOKEN_KEY
|| !e.newValue
|| !loadedUser
|| loadedUser.access_token === e.newValue
) return
userManager.storeUser({
...loadedUser,
access_token: e.newValue,
toStorageString: loadedUser.toStorageString,
})
}, []),
event: 'storage',
})
useEffect(() => {
const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkUser,
signinRedirectCallback,
login,
])
const reChekNewDevice = useCallback(async () => {
const loadedUser = await userManager.getUser()
if (!loadedUser) return
checkDevice(loadedUser.access_token).catch((er:FailedResponse) => {
if (er.error) return
if (isBoolean(er.ok) && !er.ok) {
setIsNewDeviceLogin(true)
setTimeout(logout, 10000)
}
})
}, [logout])
const checkNewDevice = useCallback(async () => {
const loadedUser = await userManager.getUser()
if (!loadedUser) return
checkDevice(loadedUser.access_token).catch(() => {
setTimeout(reChekNewDevice, 5000)
})
}, [reChekNewDevice])
useEffect(() => {
if (!needCheckNewDeviсe && !user) return undefined
const startCheckDevice = setInterval(checkNewDevice, 20000)
isNewDeviceLogin && clearInterval(startCheckDevice)
return () => clearInterval(startCheckDevice)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkNewDevice,
isNewDeviceLogin,
setIsNewDeviceLogin,
])
useEffect(() => {
// попытаемся обновить токен используя refresh_token
const tryRenewToken = () => {
const tokenLastUpdated = Number(localStorage.getItem('token_updated'))
// предотвращаем одновременное обновление токена в разных окнах/вкладках
const needRenewToken = Date.now() - tokenLastUpdated >= userManager.settings.clockSkew! * 1e3
if (!needRenewToken) return
localStorage.setItem('token_updated', String(Date.now()))
userManager.signinSilent()
.catch(() => user && logout())
}
// если запросы вернули 401 | 403
window.addEventListener('FORBIDDEN_REQUEST', tryRenewToken)
// и если токен истек
userManager.events.addAccessTokenExpired(tryRenewToken)
return () => {
window.removeEventListener('FORBIDDEN_REQUEST', tryRenewToken)
userManager.events.removeAccessTokenExpired(tryRenewToken)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [logout])
const fetchUserInfo = useCallback(async () => {
try {
const userInfoFetched = await getUserInfo()
setUserInfo(userInfoFetched)
userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)
// eslint-disable-next-line no-empty
} catch (error) {}
}, [changeLang])
useEffect(() => {
fetchUserInfo()
}, [fetchUserInfo, user])
const auth = useMemo(() => ({
fetchUserInfo,
isFromLanding,
isNewDeviceLogin,
loadingUser,
login,
logout,
page,
setIsFromLanding,
setPage,
setSearch,
setUserInfo,
user,
userInfo,
}), [
fetchUserInfo,
isNewDeviceLogin,
logout,
user,
userInfo,
login,
loadingUser,
setSearch,
setPage,
setUserInfo,
page,
setIsFromLanding,
isFromLanding,
])
return auth
}