mirror of
https://github.com/arthurfiorette/axios-cache-interceptor.git
synced 2025-12-08 17:36:16 +00:00
Feature: whitelist paths (#1008)
Co-authored-by: thomassth <8331853+thomassth@users.noreply.github.com>
This commit is contained in:
parent
6d578087c6
commit
6ca387efd9
@ -182,9 +182,11 @@ exceptions to the method rule.
|
||||
_(These default status codes follows RFC 7231)_
|
||||
|
||||
An object or function that will be tested against the response to indicate if it can be
|
||||
cached. You can use `statusCheck`, `containsHeader`, `ignoreUrls` and `responseMatch` to test against
|
||||
cached. You can use `statusCheck`, `containsHeader`, `ignoreUrls`, `allowUrls` and `responseMatch` to test against
|
||||
the response.
|
||||
|
||||
If both `ignoreUrls` & `allowUrls` are matched, `ignoreUrls` take precedence.
|
||||
|
||||
```ts{5,8,13}
|
||||
axios.get<{ auth: { status: string } }>('url', {
|
||||
cache: {
|
||||
@ -205,6 +207,9 @@ axios.get<{ auth: { status: string } }>('url', {
|
||||
|
||||
// Ensures no request is cached if its url starts with "/api"
|
||||
ignoreUrls: [/^\/api/]
|
||||
|
||||
// only cache request urls that includes "weekly"
|
||||
allowUrls: ['weekly']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import { deferred } from 'fast-defer';
|
||||
import type { AxiosCacheInstance, CacheAxiosResponse } from '../cache/axios.js';
|
||||
import { Header } from '../header/headers.js';
|
||||
import type { CachedResponse, CachedStorageValue, LoadingStorageValue } from '../storage/types.js';
|
||||
import { regexOrStringMatch } from '../util/cache-predicate.js';
|
||||
import type { RequestInterceptor } from './build.js';
|
||||
import {
|
||||
type ConfigWithCache,
|
||||
@ -29,19 +30,14 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance): RequestInt
|
||||
// merge defaults with per request configuration
|
||||
config.cache = { ...axios.defaults.cache, ...config.cache };
|
||||
|
||||
// ignoreUrls (blacklist)
|
||||
if (
|
||||
typeof config.cache.cachePredicate === 'object' &&
|
||||
config.cache.cachePredicate.ignoreUrls &&
|
||||
config.url
|
||||
) {
|
||||
for (const url of config.cache.cachePredicate.ignoreUrls) {
|
||||
if (
|
||||
url instanceof RegExp
|
||||
? // Handles stateful regexes
|
||||
// biome-ignore lint: reduces the number of checks
|
||||
((url.lastIndex = 0), url.test(config.url))
|
||||
: config.url.includes(url)
|
||||
) {
|
||||
if (regexOrStringMatch(url, config.url)) {
|
||||
if (__ACI_DEV__) {
|
||||
axios.debug({
|
||||
id: config.id,
|
||||
@ -58,6 +54,47 @@ export function defaultRequestInterceptor(axios: AxiosCacheInstance): RequestInt
|
||||
}
|
||||
}
|
||||
|
||||
// allowUrls
|
||||
if (
|
||||
typeof config.cache.cachePredicate === 'object' &&
|
||||
config.cache.cachePredicate.allowUrls &&
|
||||
config.url
|
||||
) {
|
||||
let matched = false;
|
||||
|
||||
for (const url of config.cache.cachePredicate.allowUrls) {
|
||||
if (regexOrStringMatch(url, config.url)) {
|
||||
matched = true;
|
||||
|
||||
if (__ACI_DEV__) {
|
||||
axios.debug({
|
||||
id: config.id,
|
||||
msg: `Cached because url (${config.url}) matches allowUrls (${config.cache.cachePredicate.allowUrls})`,
|
||||
data: {
|
||||
url: config.url,
|
||||
cachePredicate: config.cache.cachePredicate
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
if (__ACI_DEV__) {
|
||||
axios.debug({
|
||||
id: config.id,
|
||||
msg: `Ignored because url (${config.url}) does not match any allowUrls (${config.cache.cachePredicate.allowUrls})`,
|
||||
data: {
|
||||
url: config.url,
|
||||
cachePredicate: config.cache.cachePredicate
|
||||
}
|
||||
});
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
// Applies sufficient headers to prevent other cache systems to work along with this one
|
||||
//
|
||||
// Its currently used before isMethodIn because if the isMethodIn returns false, the request
|
||||
|
||||
@ -35,3 +35,21 @@ export async function testCachePredicate<R = unknown, D = unknown>(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a given URL matches a specified pattern, which can be either a string or a regular expression.
|
||||
*
|
||||
* @param matchPattern - The pattern to match against
|
||||
* - If it's a regular expression, it will be reset to ensure consistent behavior for stateful regular expressions.
|
||||
* - If it's a string, the function checks if the URL contains the string.
|
||||
* @param configUrl - The URL to test against the provided pattern; normally `config.url`.
|
||||
* @returns `true` if the `configUrl` matches the `matchPattern`
|
||||
*/
|
||||
export function regexOrStringMatch(matchPattern: string | RegExp, configUrl: string) {
|
||||
if (matchPattern instanceof RegExp) {
|
||||
matchPattern.lastIndex = 0; // Reset the regex to ensure consistent matching
|
||||
return matchPattern.test(configUrl);
|
||||
}
|
||||
|
||||
return configUrl.includes(matchPattern);
|
||||
}
|
||||
|
||||
@ -42,6 +42,15 @@ export interface CachePredicateObject<R = unknown, D = unknown> {
|
||||
* - When only `baseURL` is specified, this property is ignored.
|
||||
*/
|
||||
ignoreUrls?: (RegExp | string)[];
|
||||
|
||||
/**
|
||||
* Ignores the request if their url does not match any provided urls and/or regexes.
|
||||
*
|
||||
* - It checks against the `request.url` property, `baseURL` is not considered.
|
||||
* - When only `baseURL` is specified, this property is ignored.
|
||||
* - If both `ignoreUrls` & `allowUrls` are matched, `ignoreUrls` take precedence.
|
||||
*/
|
||||
allowUrls?: (RegExp | string)[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -453,6 +453,165 @@ describe('Request Interceptor', () => {
|
||||
assert.equal(req5.stale, undefined);
|
||||
});
|
||||
|
||||
it('ensures request with urls in allowUrls are cached', async () => {
|
||||
const axios = mockAxios({
|
||||
cachePredicate: {
|
||||
allowUrls: ['keep']
|
||||
}
|
||||
});
|
||||
|
||||
const [req0, req1] = await Promise.all([axios.get('url'), axios.get('url')]);
|
||||
|
||||
assert.equal(req0.cached, false);
|
||||
assert.equal(req0.stale, undefined);
|
||||
assert.equal(req1.cached, false);
|
||||
assert.equal(req1.stale, undefined);
|
||||
|
||||
const [req2, req3] = await Promise.all([axios.get('keep-url'), axios.get('keep-url')]);
|
||||
|
||||
assert.equal(req2.cached, false);
|
||||
assert.equal(req2.stale, undefined);
|
||||
assert.ok(req3.cached);
|
||||
assert.equal(req3.stale, false);
|
||||
});
|
||||
|
||||
it('ensures request with urls in allowUrls are cached (regex)', async () => {
|
||||
const axios = mockAxios({
|
||||
cachePredicate: {
|
||||
allowUrls: [/keep/]
|
||||
}
|
||||
});
|
||||
|
||||
const [req0, req1] = await Promise.all([axios.get('my/url'), axios.get('my/url')]);
|
||||
|
||||
assert.equal(req0.cached, false);
|
||||
assert.equal(req0.stale, undefined);
|
||||
assert.equal(req1.cached, false);
|
||||
assert.equal(req1.stale, undefined);
|
||||
|
||||
const [req2, req3] = await Promise.all([axios.get('keep-url'), axios.get('keep-url')]);
|
||||
|
||||
assert.equal(req2.cached, false);
|
||||
assert.equal(req2.stale, undefined);
|
||||
assert.ok(req3.cached);
|
||||
assert.equal(req3.stale, false);
|
||||
|
||||
const [req4, req5] = await Promise.all([axios.get('keep/url'), axios.get('keep/url')]);
|
||||
|
||||
assert.equal(req4.cached, false);
|
||||
assert.equal(req4.stale, undefined);
|
||||
assert.ok(req5.cached);
|
||||
assert.equal(req5.stale, false);
|
||||
});
|
||||
|
||||
it('ensures request with urls matching ignoreUrls and allowUrls are not cached', async () => {
|
||||
const axios = mockAxios({
|
||||
cachePredicate: {
|
||||
ignoreUrls: ['ignore'],
|
||||
allowUrls: ['keep']
|
||||
}
|
||||
});
|
||||
|
||||
const [req0, req1] = await Promise.all([axios.get('ignore/link'), axios.get('ignore/link')]);
|
||||
|
||||
assert.equal(req0.cached, false);
|
||||
assert.equal(req0.stale, undefined);
|
||||
assert.equal(req1.cached, false);
|
||||
assert.equal(req1.stale, undefined);
|
||||
|
||||
const [req2, req3] = await Promise.all([axios.get('keep/link'), axios.get('keep/link')]);
|
||||
|
||||
assert.equal(req2.cached, false);
|
||||
assert.equal(req2.stale, undefined);
|
||||
assert.ok(req3.cached);
|
||||
assert.equal(req3.stale, false);
|
||||
|
||||
const [req4, req5] = await Promise.all([axios.get('keep/ignore'), axios.get('keep/ignore')]);
|
||||
|
||||
assert.equal(req4.cached, false);
|
||||
assert.equal(req4.stale, undefined);
|
||||
assert.equal(req5.cached, false);
|
||||
assert.equal(req5.stale, undefined);
|
||||
|
||||
const [req6, req7] = await Promise.all([
|
||||
axios.get('ignore/ignore'),
|
||||
axios.get('ignore/ignore')
|
||||
]);
|
||||
|
||||
assert.equal(req6.cached, false);
|
||||
assert.equal(req6.stale, undefined);
|
||||
assert.equal(req7.cached, false);
|
||||
assert.equal(req7.stale, undefined);
|
||||
|
||||
const [req8, req9] = await Promise.all([axios.get('ignore/keep'), axios.get('ignore/keep')]);
|
||||
|
||||
assert.equal(req8.cached, false);
|
||||
assert.equal(req8.stale, undefined);
|
||||
assert.equal(req9.cached, false);
|
||||
assert.equal(req9.stale, undefined);
|
||||
|
||||
const [req10, req11] = await Promise.all([axios.get('keep/keep'), axios.get('keep/keep')]);
|
||||
|
||||
assert.equal(req10.cached, false);
|
||||
assert.equal(req10.stale, undefined);
|
||||
assert.ok(req11.cached);
|
||||
assert.equal(req11.stale, false);
|
||||
});
|
||||
|
||||
it('ensures request with urls matching ignoreUrls and allowUrls are not cached (regex)', async () => {
|
||||
const axios = mockAxios({
|
||||
cachePredicate: {
|
||||
ignoreUrls: [/ignore/],
|
||||
allowUrls: [/keep/]
|
||||
}
|
||||
});
|
||||
|
||||
const [req0, req1] = await Promise.all([axios.get('ignore/link'), axios.get('ignore/link')]);
|
||||
|
||||
assert.equal(req0.cached, false);
|
||||
assert.equal(req0.stale, undefined);
|
||||
assert.equal(req1.cached, false);
|
||||
assert.equal(req1.stale, undefined);
|
||||
|
||||
const [req2, req3] = await Promise.all([axios.get('keep/link'), axios.get('keep/link')]);
|
||||
|
||||
assert.equal(req2.cached, false);
|
||||
assert.equal(req2.stale, undefined);
|
||||
assert.ok(req3.cached);
|
||||
assert.equal(req3.stale, false);
|
||||
|
||||
const [req4, req5] = await Promise.all([axios.get('keep/ignore'), axios.get('keep/ignore')]);
|
||||
|
||||
assert.equal(req4.cached, false);
|
||||
assert.equal(req4.stale, undefined);
|
||||
assert.equal(req5.cached, false);
|
||||
assert.equal(req5.stale, undefined);
|
||||
|
||||
const [req6, req7] = await Promise.all([
|
||||
axios.get('ignore/ignore'),
|
||||
axios.get('ignore/ignore')
|
||||
]);
|
||||
|
||||
assert.equal(req6.cached, false);
|
||||
assert.equal(req6.stale, undefined);
|
||||
assert.equal(req7.cached, false);
|
||||
assert.equal(req7.stale, undefined);
|
||||
|
||||
const [req8, req9] = await Promise.all([axios.get('ignore/keep'), axios.get('ignore/keep')]);
|
||||
|
||||
assert.equal(req8.cached, false);
|
||||
assert.equal(req8.stale, undefined);
|
||||
assert.equal(req9.cached, false);
|
||||
assert.equal(req9.stale, undefined);
|
||||
|
||||
const [req10, req11] = await Promise.all([axios.get('keep/keep'), axios.get('keep/keep')]);
|
||||
|
||||
assert.equal(req10.cached, false);
|
||||
assert.equal(req10.stale, undefined);
|
||||
assert.ok(req11.cached);
|
||||
assert.equal(req11.stale, false);
|
||||
});
|
||||
|
||||
it('clone works with concurrent requests', async () => {
|
||||
const axios = mockAxios(
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user