Ver código fonte

Merge pull request #7871 from overleaf/ii-fetch-json-ts

Convert fetch-json to TS

GitOrigin-RevId: 78b0835aca4ba82c38004f37bb5c38ec7fb1b32c
ilkin-overleaf 4 anos atrás
pai
commit
30b07f3d34

+ 1 - 1
services/web/frontend/js/features/settings/context/user-email-context.tsx

@@ -206,7 +206,7 @@ function useUserEmails() {
   const { data, isLoading, isError, isSuccess, runAsync } = useAsync()
 
   const getEmails = useCallback(() => {
-    runAsync<UserEmailData[]>(getJSON('/user/emails?ensureAffiliation=true'))
+    runAsync(getJSON<UserEmailData[]>('/user/emails?ensureAffiliation=true'))
       .then(data => {
         dispatch(ActionCreators.setData(data))
       })

+ 57 - 97
services/web/frontend/js/infrastructure/fetch-json.js → services/web/frontend/js/infrastructure/fetch-json.ts

@@ -5,126 +5,86 @@
 // - parse JSON response body, unless response is empty
 import OError from '@overleaf/o-error'
 
-/**
- * @typedef {Object} FetchOptions
- * @extends RequestInit
- * @property {Object} [body]
- * @property {Boolean} [swallowAbortError] Set to false for throwing AbortErrors.
- * @property {AbortSignal} [signal] Allows aborting a request via AbortController
- */
-
-/**
- * @param {string} path
- * @param {FetchOptions} [options]
- */
-export function getJSON(path, options) {
-  return fetchJSON(path, { ...options, method: 'GET' })
+type FetchPath = string
+// Custom config types are merged with `fetch`s RequestInit type
+type FetchConfig = {
+  swallowAbortError?: boolean
+  body?: Record<string, unknown>
+} & Omit<RequestInit, 'body'>
+
+export function getJSON<T = any>(path: FetchPath, options?: FetchConfig) {
+  return fetchJSON<T>(path, { ...options, method: 'GET' })
 }
 
-/**
- * @param {string} path
- * @param {FetchOptions} [options]
- */
-export function postJSON(path, options) {
-  return fetchJSON(path, { ...options, method: 'POST' })
+export function postJSON<T = any>(path: FetchPath, options?: FetchConfig) {
+  return fetchJSON<T>(path, { ...options, method: 'POST' })
 }
 
-/**
- * @param {string} path
- * @param {FetchOptions} [options]
- */
-export function putJSON(path, options) {
-  return fetchJSON(path, { ...options, method: 'PUT' })
+export function putJSON<T = any>(path: FetchPath, options?: FetchConfig) {
+  return fetchJSON<T>(path, { ...options, method: 'PUT' })
 }
 
-/**
- * @param {string} path
- * @param {FetchOptions} [options]
- */
-export function deleteJSON(path, options) {
-  return fetchJSON(path, { ...options, method: 'DELETE' })
+export function deleteJSON<T = any>(path: FetchPath, options?: FetchConfig) {
+  return fetchJSON<T>(path, { ...options, method: 'DELETE' })
 }
 
-/**
- * @param {number} statusCode
- * @returns {string}
- */
-function getErrorMessageForStatusCode(statusCode) {
-  switch (statusCode) {
-    case 400:
-      return 'Bad Request'
-    case 401:
-      return 'Unauthorized'
-    case 403:
-      return 'Forbidden'
-    case 404:
-      return 'Not Found'
-    case 429:
-      return 'Too Many Requests'
-    case 500:
-      return 'Internal Server Error'
-    case 502:
-      return 'Bad Gateway'
-    case 503:
-      return 'Service Unavailable'
-    default:
-      return `Unexpected Error: ${statusCode}`
+function getErrorMessageForStatusCode(statusCode?: number) {
+  if (!statusCode) {
+    return 'Unknown Error'
   }
+
+  const statusCodes: { readonly [K: number]: string } = {
+    400: 'Bad Request',
+    401: 'Unauthorized',
+    403: 'Forbidden',
+    404: 'Not Found',
+    429: 'Too Many Requests',
+    500: 'Internal Server Error',
+    502: 'Bad Gateway',
+    503: 'Service Unavailable',
+  }
+
+  return statusCodes[statusCode] ?? `Unexpected Error: ${statusCode}`
 }
 
 export class FetchError extends OError {
-  /**
-   * @param {string} message
-   * @param {string} url
-   * @param {FetchOptions} [options]
-   * @param {Response} [response]
-   * @param {Object} [data]
-   */
-  constructor(message, url, options, response, data) {
+  constructor(
+    message: string,
+    public url: string,
+    public options?: RequestInit,
+    public response?: Response,
+    public data?: any
+  ) {
     // On HTTP2, the `statusText` property is not set,
     // so this `message` will be undefined. We need to
     // set a message based on the response `status`, so
     // our error UI rendering will work
     if (!message) {
-      message = getErrorMessageForStatusCode(response.status)
+      message = getErrorMessageForStatusCode(response?.status)
     }
     super(message, { statusCode: response ? response.status : undefined })
-    this.url = url
-    this.options = options
-    this.response = response
-    this.data = data
   }
 
-  /**
-   * @returns {string}
-   */
   getUserFacingMessage() {
     const statusCode = this.response?.status
     const defaultMessage = getErrorMessageForStatusCode(statusCode)
     const message = this.data?.message?.text || this.data?.message
     if (message && message !== defaultMessage) return message
 
-    switch (statusCode) {
-      case 400:
-        return 'Invalid Request. Please correct the data and try again.'
-      case 403:
-        return 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.'
-      case 429:
-        return 'Too many attempts. Please wait for a while and try again.'
-      default:
-        return 'Something went wrong. Please try again.'
+    const statusCodes: { readonly [K: number]: string } = {
+      400: 'Invalid Request. Please correct the data and try again.',
+      403: 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.',
+      429: 'Too many attempts. Please wait for a while and try again.',
     }
+
+    return statusCode && statusCodes[statusCode]
+      ? statusCodes[statusCode]
+      : 'Something went wrong. Please try again.'
   }
 }
 
-/**
- * @param {string} path
- * @param {FetchOptions} [options]
- *
- * @return Promise<Object>
- */
-function fetchJSON(
-  path,
+function fetchJSON<T>(
+  path: FetchPath,
   {
     body = {},
     headers = {},
@@ -132,9 +92,9 @@ function fetchJSON(
     credentials = 'same-origin',
     swallowAbortError = true,
     ...otherOptions
-  }
+  }: FetchConfig
 ) {
-  const options = {
+  const options: RequestInit = {
     ...otherOptions,
     headers: {
       ...headers,
@@ -155,7 +115,7 @@ function fetchJSON(
   // after a component has unmounted.
   // `resolve` will be called when the request succeeds, `reject` will be called when the request fails,
   // but nothing will be called if the request is cancelled via an AbortController.
-  return new Promise((resolve, reject) => {
+  return new Promise<T>((resolve, reject) => {
     fetch(path, options).then(
       response => {
         return parseResponseBody(response).then(
@@ -206,13 +166,13 @@ function fetchJSON(
   })
 }
 
-/**
- * @param {Response} response
- * @returns {Promise<Object>}
- */
-async function parseResponseBody(response) {
+async function parseResponseBody(response: Response) {
   const contentType = response.headers.get('Content-Type')
 
+  if (!contentType) {
+    return {}
+  }
+
   if (/application\/json/.test(contentType)) {
     return response.json()
   }

+ 1 - 0
services/web/types/window.ts

@@ -4,6 +4,7 @@ import { OAuthProviders } from './oauth-providers'
 declare global {
   // eslint-disable-next-line no-unused-vars
   interface Window {
+    csrfToken: string
     sl_debugging: boolean
     user: {
       id: string