Last active
February 26, 2023 14:01
-
-
Save gugadev/36483bae3aec4b283b5af8653ca699cb to your computer and use it in GitHub Desktop.
Small proxy based interceptor for fetch. Currently works with Response.prototype.text, Response.prototype.json, Response.prototype.blob and Response.prototype.arrayBuffer.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | |
/** | |
* @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) { | |
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 target.call(obj, args) | |
}, | |
}) | |
} | |
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"] | |
}) | |
// 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. Response instance methods calls. | |
try { | |
Response.prototype.text = text | |
Response.prototype.json = json | |
Response.prototype.blob = blob | |
Response.prototype.arrayBuffer = arrayBuffer | |
Object.freeze(Response.prototype) // do not use seal | |
} 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) | |
} | |
} | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment