[ABANDONED] React/Redux front end for the Flexor social network.
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.

140 lines
4.0 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. import {
  2. UnauthorizedError,
  3. BadRequestError,
  4. NotFoundError,
  5. ServerError,
  6. } from './errors'
  7. import { pathJoin } from '../utils'
  8. import {
  9. LOCAL_STORAGE_ACCESS_TOKEN_KEY,
  10. LOCAL_STORAGE_ACCESS_TOKEN_EXPIRES_AT_KEY,
  11. LOCAL_STORAGE_REFRESH_TOKEN_KEY,
  12. } from '../constants'
  13. import { FetchOptions, FormNotification, NotificationType } from '../types'
  14. interface RefreshResponse {
  15. id: string
  16. access: string
  17. refresh: string
  18. expires: number
  19. }
  20. interface FormError {
  21. field?: string
  22. message: string
  23. }
  24. interface ErrorResponse {
  25. message: string
  26. errors?: FormError[]
  27. }
  28. type APIFetch = <T = void>(options: FetchOptions) => Promise<T>
  29. const mapErrorsToFormNotifications = (errors?: FormError[]): FormNotification[] => {
  30. if (!errors) return []
  31. return errors.map(e => ({
  32. field: e.field,
  33. type: NotificationType.Error,
  34. message: e.message,
  35. }))
  36. }
  37. const checkResponse = async (response: Response, retry?: () => Promise<void>) => {
  38. switch (response.status) {
  39. case 400: {
  40. const { message, errors } = await response.json() as ErrorResponse
  41. throw new BadRequestError(message, mapErrorsToFormNotifications(errors))
  42. }
  43. case 401: {
  44. if (retry) return await retry()
  45. throw new UnauthorizedError()
  46. }
  47. case 404: throw new NotFoundError()
  48. default: {
  49. throw new ServerError()
  50. }
  51. }
  52. }
  53. const getResponseData = async (response: Response) => {
  54. try {
  55. return await response.json()
  56. } catch (err) {
  57. return {}
  58. }
  59. }
  60. export const apiFetch: APIFetch = async (options: FetchOptions) => {
  61. const { path, method = 'get', body } = options
  62. const contentType = 'application/json'
  63. const doFetch = async () => {
  64. const headers = new Headers({
  65. ...options.headers,
  66. 'Accept': contentType,
  67. })
  68. if (body) headers.append('Content-Type', contentType)
  69. const accessToken = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY)
  70. if (accessToken) headers.append('Authorization', `Bearer ${accessToken}`)
  71. return await fetch(`/${pathJoin('api', path)}`, {
  72. headers,
  73. method,
  74. body: body ? JSON.stringify(body) : undefined,
  75. })
  76. }
  77. const doRefresh = async () => {
  78. const accessToken = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY)
  79. const refreshToken = localStorage.getItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY)
  80. if (accessToken && refreshToken) {
  81. const refreshResponse = await fetch('/api/v1/refresh', {
  82. headers: new Headers({
  83. 'Content-Type': contentType,
  84. 'Authorization': `Bearer ${accessToken}`
  85. }),
  86. method: 'post',
  87. body: JSON.stringify({
  88. refresh: refreshToken,
  89. }),
  90. })
  91. if (refreshResponse.status !== 201) {
  92. throw new UnauthorizedError()
  93. }
  94. const data = await getResponseData(refreshResponse) as RefreshResponse
  95. localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, data.access)
  96. localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_EXPIRES_AT_KEY, data.expires.toString())
  97. localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, data.refresh)
  98. const secondResponse = await doFetch()
  99. if (secondResponse.ok) {
  100. return await getResponseData(secondResponse)
  101. }
  102. await checkResponse(secondResponse)
  103. }
  104. }
  105. const accessTokenExpiresAt = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_EXPIRES_AT_KEY)
  106. if (accessTokenExpiresAt && Date.now() >= parseInt(accessTokenExpiresAt, 10)) {
  107. return await doRefresh()
  108. }
  109. const response = await doFetch()
  110. if (response.ok) {
  111. return await getResponseData(response)
  112. }
  113. return await checkResponse(response, doRefresh)
  114. }