use-refresh-token.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use client'
  2. import { useCallback, useEffect, useRef } from 'react'
  3. import { jwtDecode } from 'jwt-decode'
  4. import dayjs from 'dayjs'
  5. import utc from 'dayjs/plugin/utc'
  6. import { useRouter } from 'next/navigation'
  7. import type { CommonResponse } from '@/models/common'
  8. import { fetchNewToken } from '@/service/common'
  9. import { fetchWithRetry } from '@/utils'
  10. dayjs.extend(utc)
  11. const useRefreshToken = () => {
  12. const router = useRouter()
  13. const timer = useRef<NodeJS.Timeout>()
  14. const advanceTime = useRef<number>(5 * 60 * 1000)
  15. const getExpireTime = useCallback((token: string) => {
  16. if (!token)
  17. return 0
  18. const decoded = jwtDecode(token)
  19. return (decoded.exp || 0) * 1000
  20. }, [])
  21. const getCurrentTimeStamp = useCallback(() => {
  22. return dayjs.utc().valueOf()
  23. }, [])
  24. const handleError = useCallback(() => {
  25. localStorage?.removeItem('is_refreshing')
  26. localStorage?.removeItem('console_token')
  27. localStorage?.removeItem('refresh_token')
  28. router.replace('/signin')
  29. }, [])
  30. const getNewAccessToken = useCallback(async () => {
  31. const currentAccessToken = localStorage?.getItem('console_token')
  32. const currentRefreshToken = localStorage?.getItem('refresh_token')
  33. if (!currentAccessToken || !currentRefreshToken) {
  34. handleError()
  35. return new Error('No access token or refresh token found')
  36. }
  37. if (localStorage?.getItem('is_refreshing') === '1') {
  38. clearTimeout(timer.current)
  39. timer.current = setTimeout(() => {
  40. getNewAccessToken()
  41. }, 1000)
  42. return null
  43. }
  44. const currentTokenExpireTime = getExpireTime(currentAccessToken)
  45. if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime) {
  46. localStorage?.setItem('is_refreshing', '1')
  47. const [e, res] = await fetchWithRetry(fetchNewToken({
  48. body: { refresh_token: currentRefreshToken },
  49. }) as Promise<CommonResponse & { data: { access_token: string; refresh_token: string } }>)
  50. if (e) {
  51. handleError()
  52. return e
  53. }
  54. const { access_token, refresh_token } = res.data
  55. localStorage?.setItem('is_refreshing', '0')
  56. localStorage?.setItem('console_token', access_token)
  57. localStorage?.setItem('refresh_token', refresh_token)
  58. const newTokenExpireTime = getExpireTime(access_token)
  59. clearTimeout(timer.current)
  60. timer.current = setTimeout(() => {
  61. getNewAccessToken()
  62. }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
  63. }
  64. else {
  65. const newTokenExpireTime = getExpireTime(currentAccessToken)
  66. clearTimeout(timer.current)
  67. timer.current = setTimeout(() => {
  68. getNewAccessToken()
  69. }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
  70. }
  71. return null
  72. }, [getExpireTime, getCurrentTimeStamp, handleError])
  73. const handleVisibilityChange = useCallback(() => {
  74. if (document.visibilityState === 'visible')
  75. getNewAccessToken()
  76. }, [])
  77. useEffect(() => {
  78. window.addEventListener('visibilitychange', handleVisibilityChange)
  79. return () => {
  80. window.removeEventListener('visibilitychange', handleVisibilityChange)
  81. clearTimeout(timer.current)
  82. localStorage?.removeItem('is_refreshing')
  83. }
  84. }, [])
  85. return {
  86. getNewAccessToken,
  87. }
  88. }
  89. export default useRefreshToken