{"version":3,"sources":["../src/playwright/setupClerkTestingToken.ts"],"sourcesContent":["import type { BrowserContext, Page } from '@playwright/test';\n\nimport type { SetupClerkTestingTokenOptions } from '../common';\nimport { ERROR_MISSING_FRONTEND_API_URL, TESTING_TOKEN_PARAM } from '../common';\n\ntype SetupClerkTestingTokenParams = {\n  context?: BrowserContext;\n  page?: Page;\n  options?: SetupClerkTestingTokenOptions;\n};\n\nconst setupContexts = new WeakSet<BrowserContext>();\n\nconst RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);\nconst MAX_ROUTE_RETRIES = 3;\nconst BASE_DELAY_MS = 500;\nconst JITTER_MAX_MS = 250;\n\n/**\n * Bypasses bot protection by appending the testing token in the Frontend API requests.\n *\n * @param params.context - The Playwright browser context object.\n * @param params.page - The Playwright page object.\n * @param params.options.frontendApiUrl - The frontend API URL for your Clerk dev instance, without the protocol.\n * @returns A promise that resolves when the bot protection bypass is set up.\n * @throws An error if the Frontend API URL is not provided.\n * @remarks Set the `CLERK_TESTING_DEBUG` environment variable to enable verbose logging of retry attempts and route handler registration.\n * @example\n * import { setupClerkTestingToken } from '@clerk/testing/playwright';\n *\n * test('should bypass bot protection', async ({ context }) => {\n *    await setupClerkTestingToken({ context });\n *    const page = await context.newPage();\n *    await page.goto('https://your-app.com');\n *    // Continue with your test...\n *  });\n */\nexport const setupClerkTestingToken = async ({ context, options, page }: SetupClerkTestingTokenParams) => {\n  const browserContext = context ?? page?.context();\n\n  if (!browserContext) {\n    throw new Error('Either context or page must be provided to setup testing token');\n  }\n\n  if (setupContexts.has(browserContext)) {\n    if (process.env.CLERK_TESTING_DEBUG) {\n      console.log('[Clerk Testing] Route handler already registered for this context, skipping duplicate setup');\n    }\n    return;\n  }\n\n  const fapiUrl = options?.frontendApiUrl || process.env.CLERK_FAPI;\n  if (!fapiUrl) {\n    throw new Error(ERROR_MISSING_FRONTEND_API_URL);\n  }\n\n  const escapedFapiUrl = fapiUrl.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n  const apiUrl = new RegExp(`^https://${escapedFapiUrl}/v1/.*?(\\\\?.*)?$`);\n\n  setupContexts.add(browserContext);\n  try {\n    await browserContext.route(apiUrl, async route => {\n      const originalUrl = new URL(route.request().url());\n      const testingToken = process.env.CLERK_TESTING_TOKEN;\n\n      if (testingToken) {\n        originalUrl.searchParams.set(TESTING_TOKEN_PARAM, testingToken);\n      }\n\n      const urlString = originalUrl.toString();\n\n      for (let attempt = 0; attempt <= MAX_ROUTE_RETRIES; attempt++) {\n        try {\n          const response = await route.fetch({ url: urlString });\n          const status = response.status();\n\n          if (RETRYABLE_STATUS_CODES.has(status)) {\n            if (attempt < MAX_ROUTE_RETRIES) {\n              const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * JITTER_MAX_MS;\n              if (process.env.CLERK_TESTING_DEBUG) {\n                console.log(\n                  `[Clerk Testing] FAPI returned ${status}, retrying (attempt ${attempt + 1}/${MAX_ROUTE_RETRIES}, delay ${Math.round(delay)}ms): ${route.request().url()}`,\n                );\n              }\n              await new Promise(resolve => setTimeout(resolve, delay));\n              continue;\n            }\n\n            console.warn(\n              `[Clerk Testing] FAPI request failed with status ${status} after ${MAX_ROUTE_RETRIES + 1} attempts: ${route.request().url()}`,\n            );\n            await route.fulfill({ response });\n            return;\n          }\n\n          const json = await response.json();\n\n          // Override captcha_bypass in /v1/client\n          if (json?.response?.captcha_bypass === false) {\n            json.response.captcha_bypass = true;\n          }\n\n          // Override captcha_bypass in piggybacking\n          if (json?.client?.captcha_bypass === false) {\n            json.client.captcha_bypass = true;\n          }\n\n          await route.fulfill({ response, json });\n          return;\n        } catch (error) {\n          if (attempt < MAX_ROUTE_RETRIES) {\n            const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * JITTER_MAX_MS;\n            if (process.env.CLERK_TESTING_DEBUG) {\n              console.log(\n                `[Clerk Testing] FAPI request error, retrying (attempt ${attempt + 1}/${MAX_ROUTE_RETRIES}, delay ${Math.round(delay)}ms): ${route.request().url()}`,\n                error,\n              );\n            }\n            await new Promise(resolve => setTimeout(resolve, delay));\n            continue;\n          }\n\n          console.warn(\n            `[Clerk Testing] FAPI request failed after ${MAX_ROUTE_RETRIES + 1} attempts: ${route.request().url()}`,\n            error,\n          );\n          await route.continue({ url: urlString }).catch(console.error);\n          return;\n        }\n      }\n    });\n  } catch (e) {\n    setupContexts.delete(browserContext);\n    throw e;\n  }\n};\n"],"mappings":"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","names":["setupContexts","RETRYABLE_STATUS_CODES","MAX_ROUTE_RETRIES","BASE_DELAY_MS","JITTER_MAX_MS","setupClerkTestingToken","context","options","page","browserContext","fapiUrl","ERROR_MISSING_FRONTEND_API_URL","escapedFapiUrl","apiUrl","route","originalUrl","testingToken","TESTING_TOKEN_PARAM","urlString","attempt","response","status","delay","resolve","json","error"]}