{"version":3,"file":"keys.mjs","names":[],"sources":["../src/keys.ts"],"sourcesContent":["import { DEV_OR_STAGING_SUFFIXES, LEGACY_DEV_INSTANCE_SUFFIXES } from './constants';\nimport { isomorphicAtob } from './isomorphicAtob';\nimport { isomorphicBtoa } from './isomorphicBtoa';\nimport type { PublishableKey } from './types';\n\n/**\n * Configuration options for parsing publishable keys.\n */\ntype ParsePublishableKeyOptions = {\n  /** Whether to throw an error if parsing fails */\n  fatal?: boolean;\n  /** Custom domain to use for satellite instances */\n  domain?: string;\n  /** Proxy URL to use instead of the decoded frontend API */\n  proxyUrl?: string;\n  /** Whether this is a satellite instance */\n  isSatellite?: boolean;\n};\n\n/** Prefix used for production publishable keys */\nconst PUBLISHABLE_KEY_LIVE_PREFIX = 'pk_live_';\n\n/** Prefix used for development publishable keys */\nconst PUBLISHABLE_KEY_TEST_PREFIX = 'pk_test_';\n\n/**\n * Regular expression that matches development frontend API keys.\n * Matches patterns like: foo-bar-13.clerk.accounts.dev.\n */\nconst PUBLISHABLE_FRONTEND_API_DEV_REGEX = /^(([a-z]+)-){2}([0-9]{1,2})\\.clerk\\.accounts([a-z.]*)(dev|com)$/i;\n\n/**\n * Converts a frontend API URL into an unpadded base64-encoded publishable key.\n *\n * @param frontendApi - The frontend API URL (e.g., 'clerk.example.com').\n * @returns An unpadded base64-encoded publishable key with appropriate prefix (pk_live_ or pk_test_).\n */\nexport function buildPublishableKey(frontendApi: string): string {\n  const isDevKey =\n    PUBLISHABLE_FRONTEND_API_DEV_REGEX.test(frontendApi) ||\n    (frontendApi.startsWith('clerk.') && LEGACY_DEV_INSTANCE_SUFFIXES.some(s => frontendApi.endsWith(s)));\n  const keyPrefix = isDevKey ? PUBLISHABLE_KEY_TEST_PREFIX : PUBLISHABLE_KEY_LIVE_PREFIX;\n  return `${keyPrefix}${isomorphicBtoa(`${frontendApi}$`).replace(/=+$/, '')}`;\n}\n\n/**\n * Derives a publishable key from the current hostname. Intended for multi-domain\n * setups (e.g. custom domains on top of a default domain) where the correct key\n * must be resolved per request.\n *\n * Pass the configured publishable key as `fallbackKey` so that development\n * instances (pk_test_) are returned as-is instead of being incorrectly derived\n * from the host (e.g. localhost).\n *\n * @example\n * // React (use window.location.hostname, not window.location.host, to avoid including the port)\n * <ClerkProvider publishableKey={publishableKeyFromHost(window.location.hostname, import.meta.env.VITE_CLERK_PUBLISHABLE_KEY)}>\n *\n * @example\n * // Express (inside clerkMiddleware callback)\n * // Validate req.hostname against a known allowlist before passing it in.\n * // When `trust proxy` is enabled, req.hostname reads from X-Forwarded-Host\n * // and can be spoofed if your proxy is not properly configured.\n * const ALLOWED_HOSTS = ['domain-a.com', 'domain-b.com'];\n * clerkMiddleware((req) => {\n *   if (!ALLOWED_HOSTS.includes(req.hostname)) throw new Error('Unknown host');\n *   return { publishableKey: publishableKeyFromHost(req.hostname, process.env.CLERK_PUBLISHABLE_KEY) };\n * })\n */\nexport function publishableKeyFromHost(host: string, fallbackKey?: string): string {\n  if (fallbackKey && isDevelopmentFromPublishableKey(fallbackKey)) {\n    return fallbackKey;\n  }\n  const hostname = host.toLowerCase().replace(/:\\d+$/, '');\n  if (!hostname) {\n    throw new Error('Host must not be empty.');\n  }\n  return buildPublishableKey(`clerk.${hostname}`);\n}\n\n/**\n * Validates that a decoded publishable key has the correct format.\n * The decoded value should be a frontend API followed by exactly one '$' at the end.\n *\n * @param decoded - The decoded publishable key string to validate.\n * @returns `true` if the decoded key has valid format, `false` otherwise.\n */\nfunction isValidDecodedPublishableKey(decoded: string): boolean {\n  if (!decoded.endsWith('$')) {\n    return false;\n  }\n\n  const withoutTrailing = decoded.slice(0, -1);\n  if (withoutTrailing.includes('$')) {\n    return false;\n  }\n\n  return withoutTrailing.includes('.');\n}\n\nexport function parsePublishableKey(\n  key: string | undefined,\n  options: ParsePublishableKeyOptions & { fatal: true },\n): PublishableKey;\nexport function parsePublishableKey(\n  key: string | undefined,\n  options?: ParsePublishableKeyOptions,\n): PublishableKey | null;\n/**\n * Parses and validates a publishable key, extracting the frontend API and instance type.\n *\n * @param key - The publishable key to parse.\n * @param options - Configuration options for parsing.\n * @param options.fatal\n * @param options.domain\n * @param options.proxyUrl\n * @param options.isSatellite\n * @returns Parsed publishable key object with instanceType and frontendApi, or null if invalid.\n *\n * @throws {Error} When options.fatal is true and key is missing or invalid.\n */\nexport function parsePublishableKey(\n  key: string | undefined,\n  options: { fatal?: boolean; domain?: string; proxyUrl?: string; isSatellite?: boolean } = {},\n): PublishableKey | null {\n  key = key || '';\n\n  if (!key || !isPublishableKey(key)) {\n    if (options.fatal && !key) {\n      throw new Error(\n        'Publishable key is missing. Ensure that your publishable key is correctly configured. Double-check your environment configuration for your keys, or access them here: https://dashboard.clerk.com/last-active?path=api-keys',\n      );\n    }\n    if (options.fatal && !isPublishableKey(key)) {\n      throw new Error('Publishable key not valid.');\n    }\n    return null;\n  }\n\n  const instanceType = key.startsWith(PUBLISHABLE_KEY_LIVE_PREFIX) ? 'production' : 'development';\n\n  let decodedFrontendApi: string;\n  try {\n    decodedFrontendApi = isomorphicAtob(key.split('_')[2]);\n  } catch {\n    if (options.fatal) {\n      throw new Error('Publishable key not valid: Failed to decode key.');\n    }\n    return null;\n  }\n\n  if (!isValidDecodedPublishableKey(decodedFrontendApi)) {\n    if (options.fatal) {\n      throw new Error('Publishable key not valid: Decoded key has invalid format.');\n    }\n    return null;\n  }\n\n  let frontendApi = decodedFrontendApi.slice(0, -1);\n\n  if (options.proxyUrl) {\n    frontendApi = options.proxyUrl;\n  } else if (instanceType !== 'development' && options.domain && options.isSatellite) {\n    frontendApi = `clerk.${options.domain}`;\n  }\n\n  return {\n    instanceType,\n    frontendApi,\n  };\n}\n\n/**\n * Checks if the provided key is a valid publishable key.\n *\n * @param key - The key to be checked. Defaults to an empty string if not provided.\n * @returns `true` if 'key' is a valid publishable key, `false` otherwise.\n */\nexport function isPublishableKey(key: string = '') {\n  try {\n    const hasValidPrefix = key.startsWith(PUBLISHABLE_KEY_LIVE_PREFIX) || key.startsWith(PUBLISHABLE_KEY_TEST_PREFIX);\n\n    if (!hasValidPrefix) {\n      return false;\n    }\n\n    const parts = key.split('_');\n    if (parts.length !== 3) {\n      return false;\n    }\n\n    const encodedPart = parts[2];\n    if (!encodedPart) {\n      return false;\n    }\n\n    const decoded = isomorphicAtob(encodedPart);\n    return isValidDecodedPublishableKey(decoded);\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Creates a memoized cache for checking if URLs are development or staging environments.\n * Uses a Map to cache results for better performance on repeated checks.\n *\n * @returns An object with an isDevOrStagingUrl method that checks if a URL is dev/staging.\n */\nexport function createDevOrStagingUrlCache() {\n  const devOrStagingUrlCache = new Map<string, boolean>();\n\n  return {\n    /**\n     * Checks if a URL is a development or staging environment.\n     *\n     * @param url - The URL to check (string or URL object).\n     * @returns `true` if the URL is a development or staging environment, `false` otherwise.\n     */\n    isDevOrStagingUrl: (url: string | URL): boolean => {\n      if (!url) {\n        return false;\n      }\n\n      const hostname = typeof url === 'string' ? url : url.hostname;\n      let res = devOrStagingUrlCache.get(hostname);\n      if (res === undefined) {\n        res = DEV_OR_STAGING_SUFFIXES.some(s => hostname.endsWith(s));\n        devOrStagingUrlCache.set(hostname, res);\n      }\n      return res;\n    },\n  };\n}\n\n/**\n * Checks if a publishable key is for a development environment.\n * Supports both legacy format (test_) and new format (pk_test_).\n *\n * @param apiKey - The API key to check.\n * @returns `true` if the key is for development, `false` otherwise.\n */\nexport function isDevelopmentFromPublishableKey(apiKey: string): boolean {\n  return apiKey.startsWith('test_') || apiKey.startsWith('pk_test_');\n}\n\n/**\n * Checks if a publishable key is for a production environment.\n * Supports both legacy format (live_) and new format (pk_live_).\n *\n * @param apiKey - The API key to check.\n * @returns `true` if the key is for production, `false` otherwise.\n */\nexport function isProductionFromPublishableKey(apiKey: string): boolean {\n  return apiKey.startsWith('live_') || apiKey.startsWith('pk_live_');\n}\n\n/**\n * Checks if a secret key is for a development environment.\n * Supports both legacy format (test_) and new format (sk_test_).\n *\n * @param apiKey - The secret key to check.\n * @returns `true` if the key is for development, `false` otherwise.\n */\nexport function isDevelopmentFromSecretKey(apiKey: string): boolean {\n  return apiKey.startsWith('test_') || apiKey.startsWith('sk_test_');\n}\n\n/**\n * Checks if a secret key is for a production environment.\n * Supports both legacy format (live_) and new format (sk_live_).\n *\n * @param apiKey - The secret key to check.\n * @returns `true` if the key is for production, `false` otherwise.\n */\nexport function isProductionFromSecretKey(apiKey: string): boolean {\n  return apiKey.startsWith('live_') || apiKey.startsWith('sk_live_');\n}\n\n/**\n * Generates a unique cookie suffix based on the publishable key using SHA-1 hashing.\n * The suffix is base64-encoded and URL-safe (+ and / characters are replaced).\n *\n * @param publishableKey - The publishable key to generate suffix from.\n * @param subtle - The SubtleCrypto interface to use for hashing (defaults to globalThis.crypto.subtle).\n * @returns A promise that resolves to an 8-character URL-safe base64 string.\n */\nexport async function getCookieSuffix(\n  publishableKey: string,\n  subtle: SubtleCrypto = globalThis.crypto.subtle,\n): Promise<string> {\n  const data = new TextEncoder().encode(publishableKey);\n  const digest = await subtle.digest('sha-1', data);\n  const stringDigest = String.fromCharCode(...new Uint8Array(digest));\n  // Base 64 Encoding with URL and Filename Safe Alphabet: https://datatracker.ietf.org/doc/html/rfc4648#section-5\n  return isomorphicBtoa(stringDigest).replace(/\\+/gi, '-').replace(/\\//gi, '_').substring(0, 8);\n}\n\n/**\n * Creates a suffixed cookie name by appending the cookie suffix to the base name.\n * Used to create unique cookie names based on the publishable key.\n *\n * @param cookieName - The base cookie name.\n * @param cookieSuffix - The suffix to append (typically generated by getCookieSuffix).\n * @returns The suffixed cookie name in format: `${cookieName}_${cookieSuffix}`.\n */\nexport const getSuffixedCookieName = (cookieName: string, cookieSuffix: string): string => {\n  return `${cookieName}_${cookieSuffix}`;\n};\n"],"mappings":";;;;;;AAoBA,MAAM,8BAA8B;;AAGpC,MAAM,8BAA8B;;;;;AAMpC,MAAM,qCAAqC;;;;;;;AAQ3C,SAAgB,oBAAoB,aAA6B;CAK/D,OAAO,GAHL,mCAAmC,KAAK,WAAW,KAClD,YAAY,WAAW,QAAQ,KAAK,6BAA6B,MAAK,MAAK,YAAY,SAAS,CAAC,CAAC,IACxE,8BAA8B,8BACrC,eAAe,GAAG,YAAY,EAAE,CAAC,CAAC,QAAQ,OAAO,EAAE;AAC3E;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,uBAAuB,MAAc,aAA8B;CACjF,IAAI,eAAe,gCAAgC,WAAW,GAC5D,OAAO;CAET,MAAM,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ,SAAS,EAAE;CACvD,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,yBAAyB;CAE3C,OAAO,oBAAoB,SAAS,UAAU;AAChD;;;;;;;;AASA,SAAS,6BAA6B,SAA0B;CAC9D,IAAI,CAAC,QAAQ,SAAS,GAAG,GACvB,OAAO;CAGT,MAAM,kBAAkB,QAAQ,MAAM,GAAG,EAAE;CAC3C,IAAI,gBAAgB,SAAS,GAAG,GAC9B,OAAO;CAGT,OAAO,gBAAgB,SAAS,GAAG;AACrC;;;;;;;;;;;;;;AAuBA,SAAgB,oBACd,KACA,UAA0F,CAAC,GACpE;CACvB,MAAM,OAAO;CAEb,IAAI,CAAC,OAAO,CAAC,iBAAiB,GAAG,GAAG;EAClC,IAAI,QAAQ,SAAS,CAAC,KACpB,MAAM,IAAI,MACR,6NACF;EAEF,IAAI,QAAQ,SAAS,CAAC,iBAAiB,GAAG,GACxC,MAAM,IAAI,MAAM,4BAA4B;EAE9C,OAAO;CACT;CAEA,MAAM,eAAe,IAAI,WAAW,2BAA2B,IAAI,eAAe;CAElF,IAAI;CACJ,IAAI;EACF,qBAAqB,eAAe,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;CACvD,QAAQ;EACN,IAAI,QAAQ,OACV,MAAM,IAAI,MAAM,kDAAkD;EAEpE,OAAO;CACT;CAEA,IAAI,CAAC,6BAA6B,kBAAkB,GAAG;EACrD,IAAI,QAAQ,OACV,MAAM,IAAI,MAAM,4DAA4D;EAE9E,OAAO;CACT;CAEA,IAAI,cAAc,mBAAmB,MAAM,GAAG,EAAE;CAEhD,IAAI,QAAQ,UACV,cAAc,QAAQ;MACjB,IAAI,iBAAiB,iBAAiB,QAAQ,UAAU,QAAQ,aACrE,cAAc,SAAS,QAAQ;CAGjC,OAAO;EACL;EACA;CACF;AACF;;;;;;;AAQA,SAAgB,iBAAiB,MAAc,IAAI;CACjD,IAAI;EAGF,IAAI,EAFmB,IAAI,WAAW,2BAA2B,KAAK,IAAI,WAAW,2BAA2B,IAG9G,OAAO;EAGT,MAAM,QAAQ,IAAI,MAAM,GAAG;EAC3B,IAAI,MAAM,WAAW,GACnB,OAAO;EAGT,MAAM,cAAc,MAAM;EAC1B,IAAI,CAAC,aACH,OAAO;EAIT,OAAO,6BADS,eAAe,WACW,CAAC;CAC7C,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,6BAA6B;CAC3C,MAAM,uCAAuB,IAAI,IAAqB;CAEtD,OAAO;;;;;;;AAOL,oBAAoB,QAA+B;EACjD,IAAI,CAAC,KACH,OAAO;EAGT,MAAM,WAAW,OAAO,QAAQ,WAAW,MAAM,IAAI;EACrD,IAAI,MAAM,qBAAqB,IAAI,QAAQ;EAC3C,IAAI,QAAQ,QAAW;GACrB,MAAM,wBAAwB,MAAK,MAAK,SAAS,SAAS,CAAC,CAAC;GAC5D,qBAAqB,IAAI,UAAU,GAAG;EACxC;EACA,OAAO;CACT,EACF;AACF;;;;;;;;AASA,SAAgB,gCAAgC,QAAyB;CACvE,OAAO,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,UAAU;AACnE;;;;;;;;AASA,SAAgB,+BAA+B,QAAyB;CACtE,OAAO,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,UAAU;AACnE;;;;;;;;AASA,SAAgB,2BAA2B,QAAyB;CAClE,OAAO,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,UAAU;AACnE;;;;;;;;AASA,SAAgB,0BAA0B,QAAyB;CACjE,OAAO,OAAO,WAAW,OAAO,KAAK,OAAO,WAAW,UAAU;AACnE;;;;;;;;;AAUA,eAAsB,gBACpB,gBACA,SAAuB,WAAW,OAAO,QACxB;CACjB,MAAM,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,cAAc;CACpD,MAAM,SAAS,MAAM,OAAO,OAAO,SAAS,IAAI;CAGhD,OAAO,eAFc,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAEhC,CAAC,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,UAAU,GAAG,CAAC;AAC9F;;;;;;;;;AAUA,MAAa,yBAAyB,YAAoB,iBAAiC;CACzF,OAAO,GAAG,WAAW,GAAG;AAC1B"}