refresh-token.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { apiPrefix } from '@/config'
  2. import { fetchWithRetry } from '@/utils'
  3. let isRefreshing = false
  4. function waitUntilTokenRefreshed() {
  5. return new Promise<void>((resolve, reject) => {
  6. function _check() {
  7. const isRefreshingSign = localStorage.getItem('is_refreshing')
  8. if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
  9. setTimeout(() => {
  10. _check()
  11. }, 1000)
  12. }
  13. else {
  14. resolve()
  15. }
  16. }
  17. _check()
  18. })
  19. }
  20. // only one request can send
  21. async function getNewAccessToken(): Promise<void> {
  22. try {
  23. const isRefreshingSign = localStorage.getItem('is_refreshing')
  24. if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) {
  25. await waitUntilTokenRefreshed()
  26. }
  27. else {
  28. globalThis.localStorage.setItem('is_refreshing', '1')
  29. isRefreshing = true
  30. const refresh_token = globalThis.localStorage.getItem('refresh_token')
  31. // Do not use baseFetch to refresh tokens.
  32. // If a 401 response occurs and baseFetch itself attempts to refresh the token,
  33. // it can lead to an infinite loop if the refresh attempt also returns 401.
  34. // To avoid this, handle token refresh separately in a dedicated function
  35. // that does not call baseFetch and uses a single retry mechanism.
  36. const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, {
  37. method: 'POST',
  38. headers: {
  39. 'Content-Type': 'application/json;utf-8',
  40. },
  41. body: JSON.stringify({ refresh_token }),
  42. }))
  43. if (error) {
  44. return Promise.reject(error)
  45. }
  46. else {
  47. if (ret.status === 401)
  48. return Promise.reject(ret)
  49. const { data } = await ret.json()
  50. globalThis.localStorage.setItem('console_token', data.access_token)
  51. globalThis.localStorage.setItem('refresh_token', data.refresh_token)
  52. }
  53. }
  54. }
  55. catch (error) {
  56. console.error(error)
  57. return Promise.reject(error)
  58. }
  59. finally {
  60. isRefreshing = false
  61. globalThis.localStorage.removeItem('is_refreshing')
  62. }
  63. }
  64. export async function refreshAccessTokenOrRelogin(timeout: number) {
  65. return Promise.race([new Promise<void>((resolve, reject) => setTimeout(() => {
  66. isRefreshing = false
  67. globalThis.localStorage.removeItem('is_refreshing')
  68. reject(new Error('request timeout'))
  69. }, timeout)), getNewAccessToken()])
  70. }