| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.createMiddleware = exports.applyMiddlewares = exports.withLogging = exports.withOAuth = void 0;
- const auth_js_1 = require("./auth.js");
- /**
- * Creates a fetch wrapper that handles OAuth authentication automatically.
- *
- * This wrapper will:
- * - Add Authorization headers with access tokens
- * - Handle 401 responses by attempting re-authentication
- * - Retry the original request after successful auth
- * - Handle OAuth errors appropriately (InvalidClientError, etc.)
- *
- * The baseUrl parameter is optional and defaults to using the domain from the request URL.
- * However, you should explicitly provide baseUrl when:
- * - Making requests to multiple subdomains (e.g., api.example.com, cdn.example.com)
- * - Using API paths that differ from OAuth discovery paths (e.g., requesting /api/v1/data but OAuth is at /)
- * - The OAuth server is on a different domain than your API requests
- * - You want to ensure consistent OAuth behavior regardless of request URLs
- *
- * For MCP transports, set baseUrl to the same URL you pass to the transport constructor.
- *
- * Note: This wrapper is designed for general-purpose fetch operations.
- * MCP transports (SSE and StreamableHTTP) already have built-in OAuth handling
- * and should not need this wrapper.
- *
- * @param provider - OAuth client provider for authentication
- * @param baseUrl - Base URL for OAuth server discovery (defaults to request URL domain)
- * @returns A fetch middleware function
- */
- const withOAuth = (provider, baseUrl) => next => {
- return async (input, init) => {
- const makeRequest = async () => {
- const headers = new Headers(init?.headers);
- // Add authorization header if tokens are available
- const tokens = await provider.tokens();
- if (tokens) {
- headers.set('Authorization', `Bearer ${tokens.access_token}`);
- }
- return await next(input, { ...init, headers });
- };
- let response = await makeRequest();
- // Handle 401 responses by attempting re-authentication
- if (response.status === 401) {
- try {
- const { resourceMetadataUrl, scope } = (0, auth_js_1.extractWWWAuthenticateParams)(response);
- // Use provided baseUrl or extract from request URL
- const serverUrl = baseUrl || (typeof input === 'string' ? new URL(input).origin : input.origin);
- const result = await (0, auth_js_1.auth)(provider, {
- serverUrl,
- resourceMetadataUrl,
- scope,
- fetchFn: next
- });
- if (result === 'REDIRECT') {
- throw new auth_js_1.UnauthorizedError('Authentication requires user authorization - redirect initiated');
- }
- if (result !== 'AUTHORIZED') {
- throw new auth_js_1.UnauthorizedError(`Authentication failed with result: ${result}`);
- }
- // Retry the request with fresh tokens
- response = await makeRequest();
- }
- catch (error) {
- if (error instanceof auth_js_1.UnauthorizedError) {
- throw error;
- }
- throw new auth_js_1.UnauthorizedError(`Failed to re-authenticate: ${error instanceof Error ? error.message : String(error)}`);
- }
- }
- // If we still have a 401 after re-auth attempt, throw an error
- if (response.status === 401) {
- const url = typeof input === 'string' ? input : input.toString();
- throw new auth_js_1.UnauthorizedError(`Authentication failed for ${url}`);
- }
- return response;
- };
- };
- exports.withOAuth = withOAuth;
- /**
- * Creates a fetch middleware that logs HTTP requests and responses.
- *
- * When called without arguments `withLogging()`, it uses the default logger that:
- * - Logs successful requests (2xx) to `console.log`
- * - Logs error responses (4xx/5xx) and network errors to `console.error`
- * - Logs all requests regardless of status (statusLevel: 0)
- * - Does not include request or response headers in logs
- * - Measures and displays request duration in milliseconds
- *
- * Important: the default logger uses both `console.log` and `console.error` so it should not be used with
- * `stdio` transports and applications.
- *
- * @param options - Logging configuration options
- * @returns A fetch middleware function
- */
- const withLogging = (options = {}) => {
- const { logger, includeRequestHeaders = false, includeResponseHeaders = false, statusLevel = 0 } = options;
- const defaultLogger = input => {
- const { method, url, status, statusText, duration, requestHeaders, responseHeaders, error } = input;
- let message = error
- ? `HTTP ${method} ${url} failed: ${error.message} (${duration}ms)`
- : `HTTP ${method} ${url} ${status} ${statusText} (${duration}ms)`;
- // Add headers to message if requested
- if (includeRequestHeaders && requestHeaders) {
- const reqHeaders = Array.from(requestHeaders.entries())
- .map(([key, value]) => `${key}: ${value}`)
- .join(', ');
- message += `\n Request Headers: {${reqHeaders}}`;
- }
- if (includeResponseHeaders && responseHeaders) {
- const resHeaders = Array.from(responseHeaders.entries())
- .map(([key, value]) => `${key}: ${value}`)
- .join(', ');
- message += `\n Response Headers: {${resHeaders}}`;
- }
- if (error || status >= 400) {
- // eslint-disable-next-line no-console
- console.error(message);
- }
- else {
- // eslint-disable-next-line no-console
- console.log(message);
- }
- };
- const logFn = logger || defaultLogger;
- return next => async (input, init) => {
- const startTime = performance.now();
- const method = init?.method || 'GET';
- const url = typeof input === 'string' ? input : input.toString();
- const requestHeaders = includeRequestHeaders ? new Headers(init?.headers) : undefined;
- try {
- const response = await next(input, init);
- const duration = performance.now() - startTime;
- // Only log if status meets the log level threshold
- if (response.status >= statusLevel) {
- logFn({
- method,
- url,
- status: response.status,
- statusText: response.statusText,
- duration,
- requestHeaders,
- responseHeaders: includeResponseHeaders ? response.headers : undefined
- });
- }
- return response;
- }
- catch (error) {
- const duration = performance.now() - startTime;
- // Always log errors regardless of log level
- logFn({
- method,
- url,
- status: 0,
- statusText: 'Network Error',
- duration,
- requestHeaders,
- error: error
- });
- throw error;
- }
- };
- };
- exports.withLogging = withLogging;
- /**
- * Composes multiple fetch middleware functions into a single middleware pipeline.
- * Middleware are applied in the order they appear, creating a chain of handlers.
- *
- * @example
- * ```typescript
- * // Create a middleware pipeline that handles both OAuth and logging
- * const enhancedFetch = applyMiddlewares(
- * withOAuth(oauthProvider, 'https://api.example.com'),
- * withLogging({ statusLevel: 400 })
- * )(fetch);
- *
- * // Use the enhanced fetch - it will handle auth and log errors
- * const response = await enhancedFetch('https://api.example.com/data');
- * ```
- *
- * @param middleware - Array of fetch middleware to compose into a pipeline
- * @returns A single composed middleware function
- */
- const applyMiddlewares = (...middleware) => {
- return next => {
- return middleware.reduce((handler, mw) => mw(handler), next);
- };
- };
- exports.applyMiddlewares = applyMiddlewares;
- /**
- * Helper function to create custom fetch middleware with cleaner syntax.
- * Provides the next handler and request details as separate parameters for easier access.
- *
- * @example
- * ```typescript
- * // Create custom authentication middleware
- * const customAuthMiddleware = createMiddleware(async (next, input, init) => {
- * const headers = new Headers(init?.headers);
- * headers.set('X-Custom-Auth', 'my-token');
- *
- * const response = await next(input, { ...init, headers });
- *
- * if (response.status === 401) {
- * console.log('Authentication failed');
- * }
- *
- * return response;
- * });
- *
- * // Create conditional middleware
- * const conditionalMiddleware = createMiddleware(async (next, input, init) => {
- * const url = typeof input === 'string' ? input : input.toString();
- *
- * // Only add headers for API routes
- * if (url.includes('/api/')) {
- * const headers = new Headers(init?.headers);
- * headers.set('X-API-Version', 'v2');
- * return next(input, { ...init, headers });
- * }
- *
- * // Pass through for non-API routes
- * return next(input, init);
- * });
- *
- * // Create caching middleware
- * const cacheMiddleware = createMiddleware(async (next, input, init) => {
- * const cacheKey = typeof input === 'string' ? input : input.toString();
- *
- * // Check cache first
- * const cached = await getFromCache(cacheKey);
- * if (cached) {
- * return new Response(cached, { status: 200 });
- * }
- *
- * // Make request and cache result
- * const response = await next(input, init);
- * if (response.ok) {
- * await saveToCache(cacheKey, await response.clone().text());
- * }
- *
- * return response;
- * });
- * ```
- *
- * @param handler - Function that receives the next handler and request parameters
- * @returns A fetch middleware function
- */
- const createMiddleware = (handler) => {
- return next => (input, init) => handler(next, input, init);
- };
- exports.createMiddleware = createMiddleware;
- //# sourceMappingURL=middleware.js.map
|