Skip to content

Instantly share code, notes, and snippets.

@gugadev
Last active February 26, 2023 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gugadev/36483bae3aec4b283b5af8653ca699cb to your computer and use it in GitHub Desktop.
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.
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