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

Convert fetch-json to TS

GitOrigin-RevId: 78b0835aca4ba82c38004f37bb5c38ec7fb1b32c
This commit is contained in:
ilkin-overleaf 2022-05-17 13:44:49 +03:00 committed by Copybot
parent 2d8e832be7
commit 30b07f3d34
3 changed files with 58 additions and 97 deletions

View file

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

View file

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

View file

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