Skip to content

Instantly share code, notes, and snippets.

@gugadev
Last active February 26, 2023 14:01
Show Gist options
  • Save gugadev/36483bae3aec4b283b5af8653ca699cb to your computer and use it in GitHub Desktop.
Save gugadev/36483bae3aec4b283b5af8653ca699cb to your computer and use it in GitHub Desktop.

Revisions

  1. gugadev revised this gist Feb 26, 2023. 1 changed file with 9 additions and 5 deletions.
    14 changes: 9 additions & 5 deletions fetch-response-interceptor.ts
    Original file line number Diff line number Diff line change
    @@ -15,14 +15,18 @@ type FetchResponseType = "text" | "json" | "blob" | "arrayBuffer"
    type FetchInterceptorArgs = {
    config: FetchInterceptorResponseConfig,
    responseTypes: FetchResponseType[]

    }

    function createFetchResponseInterceptor({ config, responseTypes }: FetchInterceptorArgs) {
    const createInterceptor = (responseType: FetchResponseType) => {
    /**
    * @description Proxifies the Response.prototype[text|json|blob|arrayBuffer]
    * instance methods to check by status codes and fire custom logic.
    * If you don't want to continue with .text(), .json(), .blob() or
    * .arrayBuffer() if there is a non 200 code, just return in every if.
    */
    const createInterceptor = (responseType: FetchResponseType): FetchResponseType => {
    return new Proxy(Response.prototype[responseType], {
    apply(target: any, obj: any, args: any) {
    const response: string | object | ArrayBuffer | Blob | FormData = target.call(obj, args)
    if (obj.status === 401 && config.onUnauthenticated) {
    config.onUnauthenticated(obj)
    }
    @@ -35,7 +39,7 @@ function createFetchResponseInterceptor({ config, responseTypes }: FetchIntercep
    if (obj.status === 502 && config.onRateLimit) {
    config.onRateLimit(obj)
    }
    return response
    return target.call(obj, args)
    },
    })
    }
    @@ -69,7 +73,7 @@ function createFetchResponseInterceptor({ config, responseTypes }: FetchIntercep
    // If you use Object.seal instead of Object.freeze,
    // the browser will attach a new interceptor every
    // time the code is re-executed, producing a duplicated,
    // truplicated, etc. the method calls.
    // truplicated, etc. Response instance methods calls.
    try {
    Response.prototype.text = text
    Response.prototype.json = json
  2. gugadev revised this gist Feb 26, 2023. 1 changed file with 8 additions and 1 deletion.
    9 changes: 8 additions & 1 deletion fetch-response-interceptor.ts
    Original file line number Diff line number Diff line change
    @@ -63,12 +63,19 @@ function createFetchResponseInterceptor({ config, responseTypes }: FetchIntercep
    responseTypes: ["text", "json", "blob", "arrayBuffer"]
    })

    // use this empty catch because we want to ignore
    // the errors when the browsers tries to assing
    // things into it's prototype because it's freezed.
    // If you use Object.seal instead of Object.freeze,
    // the browser will attach a new interceptor every
    // time the code is re-executed, producing a duplicated,
    // truplicated, etc. the method calls.
    try {
    Response.prototype.text = text
    Response.prototype.json = json
    Response.prototype.blob = blob
    Response.prototype.arrayBuffer = arrayBuffer
    Object.freeze(Response.prototype)
    Object.freeze(Response.prototype) // do not use seal
    } catch {}

    const statusCodes = [200, 401, 403, 500]
  3. gugadev created this gist Feb 26, 2023.
    83 changes: 83 additions & 0 deletions fetch-response-interceptor.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    type InterceptorResponse = Omit<
    typeof Response.prototype,
    'text' | 'formData' | 'blob' | 'json' | 'arrayBuffer'
    >

    type FetchInterceptorResponseConfig = {
    onUnauthenticated?: (obj: InterceptorResponse) => void
    onInternalError?: (obj: InterceptorResponse) => void
    onForbidden?: (obj: InterceptorResponse) => void
    onRateLimit?: (obj: InterceptorResponse) => void
    }

    type FetchResponseType = "text" | "json" | "blob" | "arrayBuffer"

    type FetchInterceptorArgs = {
    config: FetchInterceptorResponseConfig,
    responseTypes: FetchResponseType[]

    }

    function createFetchResponseInterceptor({ config, responseTypes }: FetchInterceptorArgs) {
    const createInterceptor = (responseType: FetchResponseType) => {
    return new Proxy(Response.prototype[responseType], {
    apply(target: any, obj: any, args: any) {
    const response: string | object | ArrayBuffer | Blob | FormData = target.call(obj, args)
    if (obj.status === 401 && config.onUnauthenticated) {
    config.onUnauthenticated(obj)
    }
    if (obj.status === 403 && config.onForbidden) {
    config.onForbidden(obj)
    }
    if (obj.status === 500 && config.onInternalError) {
    config.onInternalError(obj)
    }
    if (obj.status === 502 && config.onRateLimit) {
    config.onRateLimit(obj)
    }
    return response
    },
    })
    }

    return responseTypes.map(responseType => createInterceptor(responseType))
    }

    /* Using interceptor */
    ;(async () => {
    const [text, json, blob, arrayBuffer] = createFetchResponseInterceptor({
    config: {
    onUnauthenticated: obj => {
    console.log(`Signing out because it's not authenticated`)
    // do sign out...
    },
    onForbidden: obj => {
    console.log(`User has not permissions to access to this resource.`)
    // do something else if you want...
    },
    onInternalError: obj => {
    console.log(`Something went wrong at server side`)
    // do something else if you want...
    },
    },
    responseTypes: ["text", "json", "blob", "arrayBuffer"]
    })

    try {
    Response.prototype.text = text
    Response.prototype.json = json
    Response.prototype.blob = blob
    Response.prototype.arrayBuffer = arrayBuffer
    Object.freeze(Response.prototype)
    } catch {}

    const statusCodes = [200, 401, 403, 500]

    for (const statusCode of statusCodes) {
    const res = await fetch(`https://httpstat.us/${statusCode}`);
    const body = await res.text();
    if (res.ok) {
    console.log("Server responded with: ", body)
    }
    }
    })()