{"id":39233952,"url":"https://github.com/appzmonster/fetch-interceptor","last_synced_at":"2026-01-17T23:44:51.258Z","repository":{"id":57098437,"uuid":"381402924","full_name":"appzmonster/fetch-interceptor","owner":"appzmonster","description":"Fetch-interceptor is a JavaScript library to enable request interceptor feature on Fetch API. The library extends Fetch API and uses fluent API design to allow chaining of one or multiple request interceptors to a Fetch API request.","archived":false,"fork":false,"pushed_at":"2021-07-26T07:42:24.000Z","size":59,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-22T14:51:27.917Z","etag":null,"topics":["api-client","delegatinghandler","fetch","fetch-api","fluent-api","intercept","interceptor","javascript","js","mock","request","request-interceptor","xhr"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appzmonster.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-29T14:56:10.000Z","updated_at":"2024-11-01T04:29:18.000Z","dependencies_parsed_at":"2022-08-20T16:51:03.380Z","dependency_job_id":null,"html_url":"https://github.com/appzmonster/fetch-interceptor","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/appzmonster/fetch-interceptor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appzmonster%2Ffetch-interceptor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appzmonster%2Ffetch-interceptor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appzmonster%2Ffetch-interceptor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appzmonster%2Ffetch-interceptor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appzmonster","download_url":"https://codeload.github.com/appzmonster/fetch-interceptor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appzmonster%2Ffetch-interceptor/sbom","scorecard":{"id":204259,"data":{"date":"2025-08-11","repo":{"name":"github.com/appzmonster/fetch-interceptor","commit":"287ae3f290baf89baf4f618b5217ada32a6f4245"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/10 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"10 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T23:21:42.456Z","repository_id":57098437,"created_at":"2025-08-16T23:21:42.456Z","updated_at":"2025-08-16T23:21:42.456Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28522313,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T22:11:28.393Z","status":"ssl_error","status_checked_at":"2026-01-17T22:11:27.841Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","delegatinghandler","fetch","fetch-api","fluent-api","intercept","interceptor","javascript","js","mock","request","request-interceptor","xhr"],"created_at":"2026-01-17T23:44:51.129Z","updated_at":"2026-01-17T23:44:51.246Z","avatar_url":"https://github.com/appzmonster.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fetch-interceptor\n\nFetch-interceptor is a JavaScript library to enable request interceptor feature on [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). The library extends Fetch API and uses [fluent API](https://en.wikipedia.org/wiki/Fluent_interface) design to allow chaining of one or multiple request interceptors to a Fetch API request.\n\nExample syntax:\n```\nfetch\n    .with(new Timing())\n    .with(new BearerTokenHandler())(\n        \"https://graph.microsoft.com/v1.0/users/me\",\n        {\n            method: 'GET'\n        }\n    );\n\n```\n\n\n### What is \"Request interceptor\"?\nRequest interceptor is a function that is invoked during an in-flight (outgoig) request and is designed to intercept a request to perform additional processing before the request is sent and after the request returns. A request interceptor can add or modify a request header before the request is sent or transform a request response from xml to json. One or multiple request interceptors can also be chained together to work on a request. This is very useful in use case where you need to perform certain action and pass the result of the action to the next interceptor in a request.\n\nIn short, a request interceptor enables the application to do additional action (intercept) before and after a request.\n\n\n## Prerequisites\nThe library extends [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and expects the browser supports Fetch API. The library does not include polyfill to enable Fetch API. You are responsible to include any polyfill for browser that doesn't support Fetch API by default.\n\n\n## Installation\nFetch-interceptor is available as NPM package.\n\n```\nnpm install @appzmonster/fetch-interceptor\n```\n\n\n## Usage\nYou can start using fetch-interceptor by enabling it globally in your JavaScript application.\n\n```\nimport { initialize } from '@appzmonster/fetch-interceptor';\ninitialize();\n```\n\nTypically, you'll code the above in `./index.js` or any JavaScript file that you use as the entry point to your application. Also, take note that you just need to invoke `initialize()` function once per application lifecycle. The `initialize()` function will check if `'fetch'` exist in the `window` object and do necessary extension.\n\n\u003eNOTE: Invoking `initialize()` multiple times does not throw error or produce any undesirable side effect.\n\n\n### Enable Request Interceptor in a Fetch Request\nIn order to use request interceptor in a fetch request, you have to add the request interceptor to the fetch request via the `with` function: \n\nUsing async / await:\n```\nlet response = await fetch.with(/*request interceptor here*/)(\"https://some-api.somedomain.com\", { method: 'GET', mode: 'cors' });\n```\n\nUsing promise\n```\nfetch.with(/*request interceptor here*/)(\"https://some-api.somedomain.com\", { method: 'GET', mode: 'cors' }).then(...\n```\n\nYou can add one or multiple request interceptors to a request. **When multiple request interceptors are added (chained together)**, these request interceptors get invoked in a **specific order**:\n\n\u003e ***When a request is outgoing***, the first added request interceptor executes first and the last added interceptor executes last. The [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) arguments, `resource` and `init` is passed to the first added request interceptor for processing or modification. The first request interceptor can modify these arguments, do some additional actions and continue passing these arguments to the next request interceptor. This process continues until there is no more request interceptor down the line. After all request interceptors have executed their work, the request is sent out to the `resource` (usually a service API uri) using the arguments provided by the last request interceptor. For example, if you add 2 request interceptors **A** and **B**, assuming **A** interceptor adds a \"`X-DataExpiry`\" header to the request, when the second **B** interceptor is invoked, it inherits the \"`X-DataExpiry`\" header added by previous **A** interceptor. **B** interceptor can overwrite the \"`X-DataExpiry`\"header from **A** if it needs to and do any other processing.\n\n\u003e ***When a request returns***, the request interceptors are invoked in the **reverse order**. Using the same example above (**A** and **B**), the second **B** interceptor gets invoked first follows by **A** interceptor. In this case, if **B** interceptor modifies the response,  **A** interceptor will receive **B** response when it is invoked and return this modified response to the caller.\n\nChaining multiple request interceptors creates a very powerful fetch request. For example, in a typical [OAuth 2.0](https://oauth.net/2/pkce/) and [Microservice](https://microservices.io/) use case, very often you need to send a request with a bearer token which you exchange with an authorization server right before you initiate the request. With a microservice architecture, you also need to track or correlate all activities across multiple services from frontend to backend. Such use case is a good fit to use or chain multiple request interceptors - add 2 request interceptors, one to handle bearer token exchange plus header injection and another interceptor to create a correlation context that gets sent from frontend to all backend microservices.\n\nThe following is an example how you can add / chain 3 request interceptors **A**, **B**, **C** to a fetch request:\n\n```\nlet response = await fetch\n    .with(new A())\n    .with(new B())\n    .with(new C())\n    (\"https://some-api.somedomain.com\", { method: 'GET', mode: 'cors' });\n```\n\u003e NOTE: Request interceptor must be an instance of `BaseInterceptor` class. We'll talk more about `BaseInterceptor` when we cover the topic of \"**Developing your own request interceptor**\" below.\n\n### Using Built-in Request Interceptors\nThe library comes with only **2 request interceptors** by default:\n\n`Timing`\n\nRecord total time elapsed (milliseconds) of a request. The time elapsed can be returned to the caller for logging purpose.\n\nExample:\n\n```\nimport { Timing } from '@appzmonster/fetch-interceptor';\n\n...\nlet product = null;\nlet productId = 123456;\nlet fetchProductTiming = new Timing();\nlet response = await fetch\n    .with(fetchProductTiming)\n    (`https://mystore.appzmonster.com/products/{productId}`,\n    { method: 'GET' });\nif (response.ok)\n{\n    product = await response.json();\n}\nconsole.log(`[getProduct] Get product took ${fetchProductTiming.elapsed()} millisecond(s)`, product);\n```\n\n\n`MockRequest`\n\nSimulate fetch request and return a mock response that you specify. It also supports response delay (delay for N number of milliseconds before returning the response) and response status code (e.g. HTTP 200, 400...etc.). `MockRequest` is very useful when you want to code without the dependency of a service API. When you're ready to intergrate with the actual service API, simply remove the `MockRequest` interceptor from the fetch request.\n\nExample:\n\n```\nlet response = await fetch\n    .with(new MockRequest({\n                delay: 1000, // Milliseconds to delay the response.\n                data: { userId: 11, name: 'appzmonster' },\n                dataType: 'application/json', // HTTP content type (e.g. application/xml)\n                ok: false, // true, false\n                status: 400, // HTTP response status code (e.g. 200, 404, 403)\n                statusText: 'Bad Request' // HTTP response status code (e.g. 'OK', 'Bad Request')\n            }))\n            (\n                \"https://weather-api.accuweather.com\", {\n                method: 'GET'\n            });\n```\n\nThe `data` property of a `MockRequest` argument is the only mandatory property you must set. The following is the **default value** of a `MockRequest` argument:\n\n```\n{\n    data: null,\n    dataType: 'application/json', // Default is json content type.\n    delay: 1000, // Delay for 1 second by default.\n    ok: true, // For Response.ok - true or false.\n    status: 200,\n    statusText: 'OK'\n}\n```\n\n### Developing Your Own Request Interceptor\nThe main intention of this fetch-interceptor library is to allow you to develop your own request interceptor based on your requirement. The library provides a `BaseInterceptor` class for you to develop your request interceptor.\n\nLet's try to walkthrough a possible request interceptor use case - assuming you need to develop a mechanism to track / correlate all activities (events / actions) of a transaction starting from frontend to backend and record these activities to application logs. Typically we call this concept as \"[correlation](https://www.oreilly.com/library/view/building-microservices-with/9781785887833/1bebcf55-05bb-44a1-a4e5-f9733b8edfe3.xhtml)\" and we need an unique transaction id typically call \"Correlation id\" to circulate among the services, starting from frontend to the backend (backend may consists of multiple services).\n\n\u003eNOTE: The following example uses ES6 class syntax. You can use prototype inheritance style if you do not want to use ES6 class. Personally i recommended using ES6 class instead of prototype inheritance style for class development.\n\n(1) Start by creating a class and extend from `BaseInterceptor`.\n\n**./src/CorrelationId.js**\n```\nimport { BaseInterceptor } from '@appzmonster/fetch-interceptor';\nclass CorrelationId extends BaseInterceptor\n{\n    ...\n}\n\nexport default CorrelationId;\n```\n\n(2) Next, create a constructor with 3 arguments; \n1. `logger` argument to store a logger object. The logger object will send activity to application logs.\n2. `activityName` argument to store the name of the activity (e.g. getUser). \n3. `generateCorrelationId` argument to store a function to generate the unique id for our correlation context. You can use NPM package such as [uuidv4](https://www.npmjs.com/package/uuidv4) for this.\n\n```\nclass CorrelationId : extends BaseInterceptor\n{\n    constructor(logger, activityName, generateCorrelationId)\n    {\n        super();\n\n        this._logger = logger;\n        this._activityName = activityName;\n        this._generateCorrelationId = generateCorrelationId;\n        if (typeof (this._generateCorrelationId) !== 'function')\n        {\n            throw new Error('[CorrelationIdInterceptor] Argument generateCorrelationId is not a function type');\n        }\n    }\n    ...\n}\n\nexport default CorrelationId;\n```\n\n(3) Next, override the `async invoke` function of the `BaseInterceptor` class. Inside the `async invoke` function, you need to invoke the `async fetch` function of the base class (`super.fetch`) and return its response. This `async invoke` function is invoked when the request interceptor gets to execute (activated) in an in-flight request.\n\n```\nclass CorrelationId :extends BaseInterceptor\n{\n    \n    //\n    // Code omitted for brevity\n    //\n    \n    ...\n\n    async invoke(resource, init)\n    {\n        // You can manipulate the resource and init here.\n\n        // Pass the manipulated resource and init to the next \n        // request interceptor or send out the request.\n        return await super.fetch(resource, init);\n    }\n}\n\nexport default CorrelationId;\n```\n\nThe below sample logic generates an unique id for the correlation context and injects it as a header to the request. For this to work, the backend service must agree to recognize the header (by header name) as the correlation context and uses the value from the header as its correlation id.\n\n```\nclass CorrelationId :extends BaseInterceptor\n{\n    \n    //\n    // Code omitted for brevity\n    //\n    \n    ...\n\n    async invoke(resource, init)\n    {\n        // Generate correlation id and convert to string.\n        const correlationIdStr = this._generateCorrelationId() + \"\";\n\n        try\n        {\n            // Inject correlation id as header to request.\n            init.headers = Object.assign({}, init.headers, { 'X-CorrelationId' : correlationIdStr });\n            \n            // Trace the activity.\n            this._logger.trace(`[${this._activityName}] (${correlationIdStr}) Sending request to '${resource}'`, init);\n\n            let response = await super.fetch(resource, init);\n\n            // You can do any response processing here.\n\n            return response;\n        }\n        catch(error)\n        {\n            // Log the activity error.\n            this._logger.error(`[${this._activityName}] (${correlationIdStr}) Request '${resource}' encounters error`, error);\n            throw error;\n        }\n        finally\n        {\n            this._logger.trace(`[${this._activityName}] (${correlationIdStr}) Request '${resource}' is successful`);\n        }\n    }\n}\n\nexport default CorrelationId;\n```\n\n(4) Construct the fetch request with your `CorrelationId` request interceptor.\n\n**./src/Home.js**\n```\nimport CorrelationId from './CorrelationId';\nimport { useLogger } from './someLogger'; // Assuming we have some third party logger.\nimport { uuid } from 'uuidv4'; // Assuming we use uuidv4 to generate unique id.\n\n...\n\nconst getUser = async (userId) =\u003e {\n    // Assuming logger is injected here for use.\n    const logger = useLogger();\n\n    let response = await fetch\n        .with(new CorrelationId(logger, 'getUser', () =\u003e uuid()))\n        (`https://mystore.appzmonster.com/users/${userId}`, { method: 'GET' });\n\n    ...\n};\n```\n\nThat's all you need to develop your very own request interceptor. You can then use it anytime you want in any fetch request moving forward.\n\n## Additional notes\n### 1. Fluent API Design\n[Fluent API](https://en.wikipedia.org/wiki/Fluent_interface) design principle is a good fit for this library because it allows the code to clearly shows the chaining of multiple request interceptors. Such clarity helps developer to easily identify the execution sequence of the request interceptors.\n\n\n### 2. Non-intrusive Fetch API Extension\nThis library does not wrap or modify the working mechanism of [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) but instead chosen an non-intrusive extension approach. A new `with ` property is attached to the `window.fetch` and all library code is encapsulated inside the `with` function. Original Fetch API remains untouched.\n\n\n### 3. 100% Compatibility with Fetch API\nIf you are already using Fetch API, they will work 100% with or without request interceptors. You do not need to forcibly use request interceptor for all your Fetch API requests. You are given the freedom to selectively apply request interceptor to selected Fetch API request. Due to this compatibility, you can slowly introduce request interceptor in your application without worrying of breaking changes.\n\n### 4. Class Design of `BaseInterceptor` Allows Constructor Dependency Injection\nYou can design your request interceptor to use external dependency object (e.g. logger) and inject these objects to the request interceptor via the constructor.\n\n\n## License\nCopyright (c) 2021 Jimmy Leong (Github: appzmonster). Licensed under the MIT License.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappzmonster%2Ffetch-interceptor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappzmonster%2Ffetch-interceptor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappzmonster%2Ffetch-interceptor/lists"}