interceptors.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import type { CosyError } from './types'
  2. import use2FAModal from '@/components/TwoFA/use2FAModal'
  3. import { useNProgress } from '@/lib/nprogress/nprogress'
  4. import { useSettingsStore, useUserStore } from '@/pinia'
  5. import router from '@/routes'
  6. import JSEncrypt from 'jsencrypt'
  7. import { storeToRefs } from 'pinia'
  8. import { http, instance } from './client'
  9. import { handleApiError, useMessageDedupe } from './error'
  10. // Setup stores and refs
  11. const user = useUserStore()
  12. const settings = useSettingsStore()
  13. const { token, secureSessionId } = storeToRefs(user)
  14. const nprogress = useNProgress()
  15. const dedupe = useMessageDedupe()
  16. // Helper function for encrypting JSON data
  17. // eslint-disable-next-line ts/no-explicit-any
  18. async function encryptJsonData(data: any): Promise<string> {
  19. const cryptoParams = await http.get('/crypto/public_key')
  20. const { public_key } = await cryptoParams
  21. // Encrypt data with RSA public key
  22. const encrypt = new JSEncrypt()
  23. encrypt.setPublicKey(public_key)
  24. return JSON.stringify({
  25. encrypted_params: encrypt.encrypt(JSON.stringify(data)),
  26. })
  27. }
  28. // Helper function for handling encrypted form data
  29. async function handleEncryptedFormData(formData: FormData): Promise<FormData> {
  30. const cryptoParams = await http.get('/crypto/public_key')
  31. const { public_key } = await cryptoParams
  32. // Extract form parameters that are not files
  33. // eslint-disable-next-line ts/no-explicit-any
  34. const formParams: Record<string, any> = {}
  35. const newFormData = new FormData()
  36. // Copy all files to new FormData
  37. for (const [key, value] of formData.entries()) {
  38. // Check if value is a File or Blob
  39. // eslint-disable-next-line ts/no-explicit-any
  40. if (typeof value !== 'string' && ((value as any) instanceof File || (value as any) instanceof Blob)) {
  41. newFormData.append(key, value)
  42. }
  43. else {
  44. // Collect non-file fields to encrypt
  45. formParams[key] = value
  46. }
  47. }
  48. // Encrypt the form parameters
  49. const encrypt = new JSEncrypt()
  50. encrypt.setPublicKey(public_key)
  51. // Add encrypted params to form data
  52. const encryptedData = encrypt.encrypt(JSON.stringify(formParams))
  53. if (encryptedData) {
  54. newFormData.append('encrypted_params', encryptedData)
  55. }
  56. return newFormData
  57. }
  58. // Setup request interceptor
  59. export function setupRequestInterceptor() {
  60. instance.interceptors.request.use(
  61. async config => {
  62. nprogress.start()
  63. if (token.value) {
  64. config.headers.Authorization = token.value
  65. }
  66. if (settings.environment.id) {
  67. config.headers['X-Node-ID'] = settings.environment.id
  68. }
  69. if (secureSessionId.value) {
  70. config.headers['X-Secure-Session-ID'] = secureSessionId.value
  71. }
  72. // Handle JSON encryption
  73. if (config.headers?.['Content-Type'] !== 'multipart/form-data;charset=UTF-8') {
  74. config.headers['Content-Type'] = 'application/json'
  75. if (config.crypto) {
  76. config.data = await encryptJsonData(config.data)
  77. }
  78. }
  79. // Handle form data with encryption
  80. else if (config.crypto && config.data instanceof FormData) {
  81. config.data = await handleEncryptedFormData(config.data)
  82. }
  83. return config
  84. },
  85. err => {
  86. return Promise.reject(err)
  87. },
  88. )
  89. }
  90. // Setup response interceptor
  91. export function setupResponseInterceptor() {
  92. instance.interceptors.response.use(
  93. response => {
  94. nprogress.done()
  95. // Check if full response is requested in config
  96. if (response?.config?.returnFullResponse) {
  97. return Promise.resolve(response)
  98. }
  99. return Promise.resolve(response.data)
  100. },
  101. async error => {
  102. nprogress.done()
  103. const otpModal = use2FAModal()
  104. // Handle authentication errors
  105. if (error?.response) {
  106. switch (error.response.status) {
  107. case 401:
  108. secureSessionId.value = ''
  109. await otpModal.open()
  110. break
  111. case 403:
  112. user.logout()
  113. await router.push('/login')
  114. return
  115. }
  116. }
  117. // Handle JSON error that comes back as Blob for blob request type
  118. if (error?.response?.data instanceof Blob && error?.response?.data?.type === 'application/json') {
  119. try {
  120. const text = await error.response.data.text()
  121. error.response.data = JSON.parse(text)
  122. }
  123. catch (e) {
  124. // If parsing fails, we'll continue with the original error.response.data
  125. console.error('Failed to parse blob error response as JSON', e)
  126. }
  127. }
  128. const err = error.response?.data as CosyError
  129. handleApiError(err, dedupe)
  130. return Promise.reject(error.response?.data)
  131. },
  132. )
  133. }
  134. export function setupInterceptors() {
  135. setupRequestInterceptor()
  136. setupResponseInterceptor()
  137. }