{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// src/index.ts\nimport {\n  AxiosError,\n  AxiosInstance,\n  AxiosResponse,\n  InternalAxiosRequestConfig,\n} from \"axios\";\n\ndeclare module \"axios\" {\n  export interface AxiosRequestConfig {\n    skipAuthRefresh?: boolean;\n  }\n}\n\nexport interface AuthTokens {\n  accessToken: string;\n  refreshToken?: string;\n}\n\n/**\n * Configuration options for the Auth Interceptor.\n */\nexport interface AuthInterceptorConfig {\n  /** * The function to call when the token expires.\n   * Should return the new access token (and refresh token if available).\n   */\n  requestRefresh: (refreshToken?: string) => Promise<AuthTokens>;\n\n  /**\n   * Function to get the current refresh token from storage.\n   */\n  getRefreshToken?: () => string | null | undefined;\n\n  /** Callback chạy khi refresh thành công */\n  onSuccess: (tokens: AuthTokens) => void;\n\n  /** Callback chạy khi refresh thất bại  */\n  onFailure: (error: any) => void;\n\n  /**  Tự custom cách gắn token vào header. Mặc định là 'Authorization: Bearer ...' */\n  attachTokenToRequest?: (\n    request: InternalAxiosRequestConfig,\n    token: string\n  ) => void;\n\n  /**\n   * Handler chạy trước mọi request.\n   * Dùng để tự động gắn Access Token vào header từ localStorage/Store.\n   */\n  headerTokenHandler?: (\n    request: InternalAxiosRequestConfig\n  ) => void | Promise<void>;\n\n  refreshTimeout?: number;\n\n  /** * error code refresh token.\n   * @default [401]\n   */\n  statusCodes?: number[];\n\n  /** * log debug interceptor.\n   * @default false\n   */\n  debug?: boolean;\n\n  /**\n   * [OPTIONAL] Check if the token in storage is valid.\n   * Used for Cross-Tab Synchronization.\n   *\n   * If this returns a string (the token), we skip the refresh and use this token.\n   * If this returns null/false, we proceed with the refresh.\n   */\n  checkTokenIsValid?: () =>\n    | Promise<string | null | false>\n    | string\n    | null\n    | false;\n}\n\n// Queue lưu các request bị fail để retry sau\ninterface FailedRequest {\n  resolve: (value: any) => void;\n  reject: (reason?: any) => void;\n  config: InternalAxiosRequestConfig;\n}\n\nconst LOCK_KEY = \"axios-auth-refresh-lock\";\n\n/**\n * Applies the authentication interceptor to an Axios instance.\n * * @param axiosInstance - The Axios instance to intercept.\n * @param config - Configuration for the interceptor.\n * * @example\n * ```ts\n * applyAuthTokenInterceptor(axios, {\n * requestRefresh: myRefreshFunction,\n * onSuccess: (tokens) => saveTokens(tokens),\n * onFailure: () => logout(),\n * });\n * ```\n */\nexport const applyAuthTokenInterceptor = (\n  axiosInstance: AxiosInstance,\n  config: AuthInterceptorConfig\n): void => {\n  // Setup Request Interceptor (Header Handler)\n  if (config.headerTokenHandler) {\n    axiosInstance.interceptors.request.use(\n      async (requestConfig) => {\n        await config.headerTokenHandler!(requestConfig);\n        return requestConfig;\n      },\n      (error) => Promise.reject(error)\n    );\n  }\n\n  let isRefreshing = false;\n  let failedQueue: FailedRequest[] = [];\n\n  const TIMEOUT_MS = config.refreshTimeout || 30000;\n\n  const log = (msg: string, ...args: any[]) => {\n    if (config.debug) {\n      console.log(\n        `%c[Auth-Queue] ${msg}`,\n        \"color: #e67e22; font-weight: bold;\",\n        ...args\n      );\n    }\n  };\n\n  // Hàm mặc định để gắn token nếu user không truyền attachTokenToRequest\n  const defaultAttachToken = (\n    request: InternalAxiosRequestConfig,\n    token: string\n  ) => {\n    request.headers.set(\"Authorization\", `Bearer ${token}`);\n  };\n\n  const processQueue = (error: any, token: string | null = null) => {\n    failedQueue.forEach((prom) => {\n      if (error) {\n        prom.reject(error);\n      } else {\n        if (token) {\n          const attachToken = config.attachTokenToRequest || defaultAttachToken;\n          attachToken(prom.config, token);\n        }\n        // Gọi lại request\n        prom.resolve(axiosInstance(prom.config));\n      }\n    });\n    failedQueue = [];\n  };\n\n  const statusCodes = config.statusCodes || [401];\n\n  axiosInstance.interceptors.response.use(\n    (response: AxiosResponse) => response,\n    async (error: AxiosError) => {\n      const originalRequest = error.config as InternalAxiosRequestConfig & {\n        _retry?: boolean;\n        skipAuthRefresh?: boolean;\n      };\n\n      if (error.response) {\n        log(\n          `🚨 Error ${error.response.status} detected from ${originalRequest?.url}`\n        );\n      }\n\n      if (originalRequest?.skipAuthRefresh) {\n        log(\"⏩ Skipping because skipAuthRefresh is set.\");\n        return Promise.reject(error);\n      }\n\n      // Nếu không phải lỗi 401 hoặc request này đã từng retry rồi -> Bỏ qua\n      if (\n        !error.response ||\n        !statusCodes.includes(error.response.status) ||\n        !originalRequest ||\n        originalRequest.skipAuthRefresh ||\n        originalRequest._retry\n      ) {\n        return Promise.reject(error);\n      }\n\n      // Đang có request khác thực hiện refresh token\n      if (isRefreshing) {\n        log(\"⏳ Refresh already in progress (Local). Adding to queue...\");\n        return new Promise((resolve, reject) => {\n          failedQueue.push({ resolve, reject, config: originalRequest });\n        });\n      }\n\n      originalRequest._retry = true;\n      isRefreshing = true;\n\n      return new Promise((resolve, reject) => {\n        // push request error into queue\n        failedQueue.push({ resolve, reject, config: originalRequest });\n\n        // Refresh\n        const performRefresh = async () => {\n          try {\n            log(\"🔒 Acquired Lock. Checking logic...\");\n\n            // 1. Cross-Tab Check: check token another Tab is refreshing?\n            if (config.checkTokenIsValid) {\n              const validToken = await config.checkTokenIsValid();\n              if (validToken && typeof validToken === \"string\") {\n                log(\"✨ Token was already refreshed by another tab. Reusing.\");\n                processQueue(null, validToken);\n                return;\n              }\n            }\n\n            // 2. Ready Refresh\n            log(\"🔄 Starting refresh token flow...\");\n            let refreshToken = config.getRefreshToken\n              ? config.getRefreshToken()\n              : undefined;\n\n            if (refreshToken === null) refreshToken = undefined;\n\n            const timeoutPromise = new Promise<never>((_, rej) => {\n              setTimeout(() => {\n                rej(new Error(`Refresh token timed out after ${TIMEOUT_MS}ms`));\n              }, TIMEOUT_MS);\n            });\n\n            // 3. Call API Refresh\n            const newTokens = await Promise.race([\n              config.requestRefresh(refreshToken),\n              timeoutPromise,\n            ]);\n\n            // 4. Success -> Callback & Process Queue\n            log(\"✅ Refresh Successful! Token updated.\");\n            config.onSuccess(newTokens);\n            processQueue(null, newTokens.accessToken);\n          } catch (err: any) {\n            log(\"❌ Refresh Failed:\", err.message);\n            processQueue(err, null);\n            config.onFailure(err);\n          } finally {\n            isRefreshing = false;\n          }\n        };\n\n        // 👇  WEB LOCKS API\n        if (typeof navigator !== \"undefined\" && navigator.locks) {\n          // Please lock the tab. If another tab already holds the lock, the code will stop here and wait.\n          navigator.locks.request(LOCK_KEY, async () => {\n            await performRefresh();\n          });\n        } else {\n          // Fallback\n          performRefresh();\n        }\n      });\n    }\n  );\n};\n"],"mappings":"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","names":["LOCK_KEY","applyAuthTokenInterceptor","axiosInstance","config","requestConfig","error","isRefreshing","failedQueue","TIMEOUT_MS","log","msg","args","defaultAttachToken","request","token","processQueue","prom","statusCodes","response","originalRequest","resolve","reject","performRefresh","validToken","refreshToken","timeoutPromise","_","rej","newTokens","err"]}