use-refresh-token.ts 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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. timer.current = setTimeout(() => {
  39. getNewAccessToken()
  40. }, 1000)
  41. return null
  42. }
  43. const currentTokenExpireTime = getExpireTime(currentAccessToken)
  44. if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime) {
  45. localStorage?.setItem('is_refreshing', '1')
  46. const [e, res] = await fetchWithRetry(fetchNewToken({
  47. body: { refresh_token: currentRefreshToken },
  48. }) as Promise<CommonResponse & { data: { access_token: string; refresh_token: string } }>)
  49. if (e) {
  50. handleError()
  51. return e
  52. }
  53. const { access_token, refresh_token } = res.data
  54. localStorage?.setItem('is_refreshing', '0')
  55. localStorage?.setItem('console_token', access_token)
  56. localStorage?.setItem('refresh_token', refresh_token)
  57. const newTokenExpireTime = getExpireTime(access_token)
  58. timer.current = setTimeout(() => {
  59. getNewAccessToken()
  60. }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
  61. }
  62. else {
  63. const newTokenExpireTime = getExpireTime(currentAccessToken)
  64. timer.current = setTimeout(() => {
  65. getNewAccessToken()
  66. }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
  67. }
  68. return null
  69. }, [getExpireTime, getCurrentTimeStamp, handleError])
  70. useEffect(() => {
  71. return () => {
  72. clearTimeout(timer.current)
  73. localStorage?.removeItem('is_refreshing')
  74. }
  75. }, [])
  76. return {
  77. getNewAccessToken,
  78. }
  79. }
  80. export default useRefreshToken