{"version":3,"file":"interceptors.mjs","sources":["../../../src/lib/services/interceptors.ts"],"sourcesContent":["/**\n * @file HTTP Interceptors\n * @description Common HTTP interceptors for auth, logging, and error handling\n */\n\nimport {\n  type ErrorInterceptor,\n  httpClient,\n  type HttpError,\n  type HttpResponse,\n  type RecoveryErrorInterceptor,\n  type RequestInterceptor,\n  type ResponseInterceptor,\n} from './http';\n\n/**\n * Auth token getter function\n */\ntype TokenGetter = () => string | null | Promise<string | null>;\n\n/**\n * Token refresh function\n */\ntype TokenRefresher = () => Promise<string | null>;\n\n/**\n * Create authentication interceptor\n */\nexport function createAuthInterceptor(\n  getToken: TokenGetter,\n  headerName: string = 'Authorization',\n  tokenPrefix: string = 'Bearer'\n): RequestInterceptor {\n  return async (config) => {\n    if (config.skipAuth === true) {\n      return config;\n    }\n\n    const token = await getToken();\n\n    if (token !== null && token !== undefined) {\n      return {\n        ...config,\n        headers: {\n          ...config.headers,\n          [headerName]: `${tokenPrefix} ${token}`,\n        },\n      };\n    }\n\n    return config;\n  };\n}\n\n/**\n * Create request logging interceptor\n */\nexport function createRequestLoggerInterceptor(\n  logger?: (message: string, data?: unknown) => void\n): RequestInterceptor {\n  const log =\n    logger ??\n    ((_msg: string, _data?: unknown): void => {\n      /* noop by default */\n    });\n  return (config) => {\n    log(`[HTTP] ${config.method ?? 'GET'} ${config.url}`, {\n      params: config.params,\n      body: config.body,\n    });\n    return config;\n  };\n}\n\n/**\n * Create response logging interceptor\n */\nexport function createResponseLoggerInterceptor(\n  logger?: (message: string, data?: unknown) => void\n): ResponseInterceptor {\n  const log =\n    logger ??\n    ((_msg: string, _data?: unknown): void => {\n      /* noop by default */\n    });\n  return (response) => {\n    log(`[HTTP] ${response.status} ${response.config.method ?? 'GET'} ${response.config.url}`, {\n      data: response.data,\n    });\n    return response;\n  };\n}\n\n/**\n * Create error logging interceptor\n */\nexport function createErrorLoggerInterceptor(\n  logger: (message: string, error?: unknown) => void = console.error\n): ErrorInterceptor {\n  return (error) => {\n    logger(`[HTTP Error] ${error.status} ${error.config.method ?? 'GET'} ${error.config.url}`, {\n      status: error.status,\n      data: error.data,\n      message: error.message,\n    });\n    return error;\n  };\n}\n\n/**\n * Create retry interceptor\n *\n * @remarks\n * This interceptor returns a RecoveryErrorInterceptor that can return either\n * an HttpError (to continue error propagation) or an HttpResponse (when retry succeeds).\n */\nexport function createRetryInterceptor(\n  maxRetries: number = 3,\n  retryDelay: number = 1000,\n  retryCondition: (error: HttpError) => boolean = (error) =>\n    error.status >= 500 || error.status === 0\n): RecoveryErrorInterceptor {\n  const retryCount = new Map<string, number>();\n\n  return async (error): Promise<HttpError | HttpResponse> => {\n    const requestKey = `${error.config.method ?? 'GET'}_${error.config.url}`;\n    const currentRetry = retryCount.get(requestKey) ?? 0;\n\n    if (currentRetry < maxRetries && retryCondition(error)) {\n      retryCount.set(requestKey, currentRetry + 1);\n\n      // Wait before retry\n      await new Promise((resolve) => setTimeout(resolve, retryDelay * Math.pow(2, currentRetry)));\n\n      // Retry the request\n      try {\n        const response = await httpClient.request(error.config);\n        retryCount.delete(requestKey);\n        // Return successful response - recovery succeeded\n        return response;\n      } catch (retryError) {\n        const currentCount = retryCount.get(requestKey) ?? 0;\n        if (currentCount >= maxRetries) {\n          retryCount.delete(requestKey);\n        }\n        throw retryError;\n      }\n    }\n\n    retryCount.delete(requestKey);\n    return error;\n  };\n}\n\n/**\n * Create token refresh interceptor\n *\n * @remarks\n * This interceptor returns a RecoveryErrorInterceptor that can return either\n * an HttpError (to continue error propagation) or an HttpResponse (when token refresh and retry succeeds).\n */\nexport function createTokenRefreshInterceptor(\n  refreshToken: TokenRefresher,\n  onRefreshFailed: () => void\n): RecoveryErrorInterceptor {\n  let isRefreshing = false;\n  let refreshPromise: Promise<string | null> | null = null;\n\n  return async (error): Promise<HttpError | HttpResponse> => {\n    // Only handle 401 errors\n    if (error.status !== 401 || error.config.skipAuth === true) {\n      return error;\n    }\n\n    // If already refreshing, wait for the refresh to complete\n    if (isRefreshing && refreshPromise !== null) {\n      try {\n        const newToken = await refreshPromise;\n        if (newToken !== null && newToken !== '') {\n          // Retry with new token - recovery succeeded\n\n          return await httpClient.request({\n            ...error.config,\n            headers: {\n              ...error.config.headers,\n              Authorization: `Bearer ${newToken}`,\n            },\n          });\n        }\n      } catch {\n        // Refresh failed, propagate original error\n        return error;\n      }\n    }\n\n    // Start token refresh\n    isRefreshing = true;\n    refreshPromise = refreshToken();\n\n    try {\n      const newToken = await refreshPromise;\n      isRefreshing = false;\n      refreshPromise = null;\n\n      if (newToken !== null && newToken !== '') {\n        // Retry with new token - recovery succeeded\n\n        return await httpClient.request({\n          ...error.config,\n          headers: {\n            ...error.config.headers,\n            Authorization: `Bearer ${newToken}`,\n          },\n        });\n      }\n\n      // Refresh returned no token\n      onRefreshFailed();\n      return error;\n    } catch {\n      isRefreshing = false;\n      refreshPromise = null;\n      onRefreshFailed();\n      return error;\n    }\n  };\n}\n\n/**\n * Create CSRF token interceptor\n */\nexport function createCsrfInterceptor(\n  getCsrfToken: () => string | null,\n  headerName: string = 'X-CSRF-Token'\n): RequestInterceptor {\n  return (config) => {\n    const method = config.method ?? 'GET';\n\n    // Only add CSRF token for state-changing requests\n    if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n      const token = getCsrfToken();\n      if (token !== null && token !== '') {\n        return {\n          ...config,\n          headers: {\n            ...config.headers,\n            [headerName]: token,\n          },\n        };\n      }\n    }\n\n    return config;\n  };\n}\n\n/**\n * Create request ID interceptor\n */\nexport function createRequestIdInterceptor(\n  headerName: string = 'X-Request-ID'\n): RequestInterceptor {\n  return (config) => {\n    const requestId = crypto.randomUUID();\n    return {\n      ...config,\n      headers: {\n        ...config.headers,\n        [headerName]: requestId,\n      },\n      meta: {\n        ...config.meta,\n        requestId,\n      },\n    };\n  };\n}\n\n/**\n * Create timing interceptor\n */\nexport function createTimingInterceptor(onTiming: (url: string, duration: number) => void): {\n  request: RequestInterceptor;\n  response: ResponseInterceptor;\n} {\n  const timings = new Map<string, number>();\n\n  return {\n    request: (config) => {\n      const key = `${config.method ?? 'GET'}_${config.url}_${Date.now()}`;\n      timings.set(key, performance.now());\n      return {\n        ...config,\n        meta: { ...config.meta, timingKey: key },\n      };\n    },\n    response: (response) => {\n      const key = response.config.meta?.timingKey as string | undefined;\n      if (key !== undefined && key !== '') {\n        const startTime = timings.get(key);\n        if (startTime !== undefined) {\n          const duration = performance.now() - startTime;\n          timings.delete(key);\n          onTiming(response.config.url, duration);\n        }\n      }\n      return response;\n    },\n  };\n}\n"],"names":["createAuthInterceptor","getToken","headerName","tokenPrefix","config","token","createRequestLoggerInterceptor","logger","log","_msg","_data","createResponseLoggerInterceptor","response","createErrorLoggerInterceptor","error","createRetryInterceptor","maxRetries","retryDelay","retryCondition","retryCount","requestKey","currentRetry","resolve","httpClient","retryError","createTokenRefreshInterceptor","refreshToken","onRefreshFailed","isRefreshing","refreshPromise","newToken","createCsrfInterceptor","getCsrfToken","method","createRequestIdInterceptor","requestId","createTimingInterceptor","onTiming","timings","key","startTime","duration"],"mappings":"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"}