refactor(shared-auth): single fetch interceptor for multiple URLs

setupFetchInterceptor now accepts a urls[] array instead of wrapping
globalThis.fetch twice (once for auth URL, once for backend URL).
Backward compatible - backendUrl still works, mapped to urls internally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-24 20:26:03 +01:00
parent a2605e8816
commit d0802362d7
2 changed files with 35 additions and 17 deletions

View file

@ -107,15 +107,10 @@ export function initializeWebAuth(config: {
const authService = _createAuthService(config);
const tokenManager = _createTokenManager(authService);
// Set up interceptor for auth URL
_setupFetchInterceptor(authService, tokenManager);
// Set up interceptor for backend URL if provided (for automatic token refresh on 401)
if (config.backendUrl) {
_setupFetchInterceptor(authService, tokenManager, {
backendUrl: config.backendUrl,
});
}
// Set up a single fetch interceptor for all relevant URLs
const urls = [config.baseUrl];
if (config.backendUrl) urls.push(config.backendUrl);
_setupFetchInterceptor(authService, tokenManager, { urls });
return { authService, tokenManager };
}

View file

@ -12,8 +12,13 @@ export interface FetchInterceptorConfig {
skipPatterns?: string[];
/**
* Backend URL to match (only intercept requests to this URL)
* @deprecated Use `urls` instead for multiple URLs
*/
backendUrl?: string;
/**
* URLs to intercept (requests matching any of these origins will be intercepted)
*/
urls?: string[];
}
/**
@ -71,13 +76,22 @@ export function setupFetchInterceptor(
const originalFetch = globalThis.fetch;
const skipPatterns = [...DEFAULT_SKIP_PATTERNS, ...(config?.skipPatterns || [])];
const backendUrl = config?.backendUrl || authService.getBaseUrl();
// Build the list of URLs to intercept:
// 1. Explicit `urls` array takes priority
// 2. Fall back to `backendUrl` (deprecated) wrapped in an array
// 3. Default to the auth service base URL
const interceptUrls: string[] = config?.urls
? config.urls
: config?.backendUrl
? [config.backendUrl]
: [authService.getBaseUrl()];
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = extractUrl(input);
// Skip intercepting if URL doesn't match criteria
if (shouldSkipInterception(url, skipPatterns, backendUrl)) {
if (shouldSkipInterception(url, skipPatterns, interceptUrls)) {
return originalFetch(input, init);
}
@ -147,7 +161,11 @@ function extractUrl(input: RequestInfo | URL): string {
/**
* Check if request should skip interception
*/
function shouldSkipInterception(url: string, skipPatterns: string[], backendUrl: string): boolean {
function shouldSkipInterception(
url: string,
skipPatterns: string[],
interceptUrls: string[]
): boolean {
if (!url) return true;
const lowerUrl = url.toLowerCase();
@ -157,14 +175,19 @@ function shouldSkipInterception(url: string, skipPatterns: string[], backendUrl:
return true;
}
// Check if URL matches backend (must include full host:port)
// Parse backendUrl to get origin (protocol + host + port)
// Check if URL matches any of the intercept URLs (must include full host:port)
try {
const backendOrigin = new URL(backendUrl).origin.toLowerCase();
const requestOrigin = new URL(url).origin.toLowerCase();
// Only intercept if request origin matches backend origin
if (requestOrigin !== backendOrigin) {
const matchesAny = interceptUrls.some((interceptUrl) => {
try {
return new URL(interceptUrl).origin.toLowerCase() === requestOrigin;
} catch {
return false;
}
});
if (!matchesAny) {
return true;
}
} catch {