{"version":3,"file":"index.mjs","names":[],"sources":["../../src/google-one-tap/index.ts"],"sourcesContent":["import type {\n  ClientConfigError,\n  CodeResponse,\n  CredentialResponse,\n  GoogleAccounts,\n  PromptMomentNotification,\n} from './types';\n\ndeclare global {\n  interface Window {\n    /** Set by the external GSI script once it loads — absent until then. */\n    google?: {\n      accounts: GoogleAccounts;\n    };\n  }\n}\n\nexport type Props = {\n  /**\n   * Opaque nonce string forwarded as-is to Google. Google echoes it back in\n   * the ID Token's `nonce` claim per the OIDC spec; the caller is responsible\n   * for any hashing/encoding required by its verifier.\n   */\n  nonce?: string;\n  client_id: string;\n  auto_select?: boolean;\n  cancel_on_tap_outside?: boolean;\n  use_fedcm_for_prompt?: boolean;\n};\n\nexport type PromptMoment = {\n  skipped: boolean;\n  dismissed: boolean;\n  dismissedReason: ReturnType<PromptMomentNotification['getDismissedReason']>;\n};\n\n/**\n * Why prompt() could not run One Tap, so the caller can decide to fall back\n * to requestCode().\n * - `fedcm_unsupported`: browser has no FedCM (Safari/Firefox/pre-117 Chrome).\n * - `script_load_failed`: the GSI script failed to load (network/CSP).\n * - `prompt_error`: GIS threw synchronously while initializing/prompting.\n */\nexport type UnsupportedReason = 'fedcm_unsupported' | 'script_load_failed' | 'prompt_error';\n\nexport type PromptResult =\n  | { authorized: true; credential: CredentialResponse }\n  | { authorized: false; moment: PromptMoment }\n  | { authorized: false; unsupported: true; reason: UnsupportedReason };\n\nlet script: HTMLScriptElement | null = null;\n\nfunction loadScript(): Promise<void> {\n  return new Promise((resolve, reject) => {\n    if (script) {\n      if (window.google?.accounts.id) {\n        resolve();\n      } else {\n        script.addEventListener('load', () => resolve());\n        script.addEventListener('error', () => {\n          script = null; // allow a later call to recreate the element instead of hanging\n          reject(new Error('Failed to load Google One Tap script'));\n        });\n      }\n      return;\n    }\n\n    script = document.createElement('script');\n    script.id = 'google-one-tap';\n    script.async = true;\n    script.defer = true;\n    script.src = 'https://accounts.google.com/gsi/client';\n    script.onload = () => resolve();\n    script.onerror = () => {\n      script = null; // allow a later call to recreate the element instead of hanging\n      reject(new Error('Failed to load Google One Tap script'));\n    };\n\n    document.head.appendChild(script);\n  });\n}\n\n/** FedCM feature detection — `IdentityCredential` is absent on Safari, Firefox and pre-117 Chrome. */\nfunction isFedcmSupported(): boolean {\n  return typeof window !== 'undefined' && 'IdentityCredential' in window;\n}\n\n/** debug: chrome://settings/content/federatedIdentityApi */\nexport async function prompt({\n  nonce,\n  client_id,\n  auto_select = false,\n  use_fedcm_for_prompt = true,\n  cancel_on_tap_outside = false,\n}: Props): Promise<PromptResult> {\n  // FedCM is mandatory for One Tap on capable browsers (GIS completed the\n  // migration in early 2025) and there is no viable legacy fallback anymore.\n  // When the caller wants FedCM but the browser lacks it, bail early instead\n  // of letting GIS throw an async `NotSupportedError: Missing request type`\n  // from navigator.credentials.get. Callers should fall back to requestCode().\n  if (use_fedcm_for_prompt && !isFedcmSupported()) {\n    return { authorized: false, unsupported: true, reason: 'fedcm_unsupported' };\n  }\n\n  try {\n    await loadScript();\n  } catch {\n    return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n  }\n\n  const accounts = window.google?.accounts;\n  if (!accounts) {\n    // The script loaded but did not expose the API — treat it as a load failure.\n    return { authorized: false, unsupported: true, reason: 'script_load_failed' };\n  }\n\n  return new Promise<PromptResult>((resolve) => {\n    let settled = false;\n    const settle = (result: PromptResult) => {\n      if (settled) return;\n      settled = true;\n      resolve(result);\n    };\n\n    try {\n      accounts.id.initialize({\n        ux_mode: 'popup',\n        context: 'signin',\n        auto_select,\n        nonce,\n        client_id,\n        use_fedcm_for_prompt,\n        cancel_on_tap_outside,\n        callback: (credential) => settle({ authorized: true, credential }),\n        native_callback: (credential) => settle({ authorized: true, credential }),\n      });\n\n      accounts.id.prompt((notification) => {\n        if (settled) return;\n\n        const skipped = notification.isSkippedMoment();\n        const dismissed = notification.isDismissedMoment();\n        const dismissedReason = notification.getDismissedReason();\n\n        if (skipped || (dismissed && dismissedReason !== 'credential_returned')) {\n          settle({\n            authorized: false,\n            moment: { skipped, dismissed, dismissedReason },\n          });\n        }\n      });\n    } catch {\n      settle({ authorized: false, unsupported: true, reason: 'prompt_error' });\n    }\n  });\n}\n\nexport type CodeProps = {\n  client_id: string;\n  /**\n   * Space-delimited OAuth scopes. Defaults to OpenID Connect scopes so the\n   * backend can exchange the code for an ID token, mirroring One Tap's intent.\n   */\n  scope?: string;\n  /** 'popup' resolves the returned promise; 'redirect' navigates away and never resolves. */\n  ux_mode?: 'popup' | 'redirect';\n  /** Required when ux_mode is 'redirect'; for 'popup' Google uses postMessage. */\n  redirect_uri?: string;\n  state?: string;\n  login_hint?: string;\n  hd?: string;\n};\n\nexport type CodeResult =\n  | { ok: true; response: CodeResponse }\n  | { ok: false; error: ClientConfigError | CodeResponse };\n\n/**\n * OAuth 2.0 authorization-code fallback for environments where One Tap / FedCM\n * is unavailable (i.e. prompt() resolved with `unsupported: true`). It reuses\n * the same GSI script, so there is no extra dependency.\n *\n * Unlike One Tap it returns an authorization `code` for the backend to\n * exchange, not an ID token, and it must be triggered by a user gesture or the\n * popup may be blocked.\n */\nexport async function requestCode({\n  client_id,\n  scope = 'openid email profile',\n  ux_mode = 'popup',\n  redirect_uri,\n  state,\n  login_hint,\n  hd,\n}: CodeProps): Promise<CodeResult> {\n  await loadScript();\n\n  const accounts = window.google?.accounts;\n  if (!accounts) {\n    // The script loaded but did not expose the API — surface a config error.\n    const error = new Error('Google Identity Services API is unavailable') as ClientConfigError;\n    error.type = 'unknown';\n    return { ok: false, error };\n  }\n\n  return new Promise<CodeResult>((resolve) => {\n    let settled = false;\n    const settle = (result: CodeResult) => {\n      if (settled) return;\n      settled = true;\n      resolve(result);\n    };\n\n    const client = accounts.oauth2.initCodeClient({\n      client_id,\n      scope,\n      ux_mode,\n      redirect_uri,\n      state,\n      login_hint,\n      hd,\n      callback: (response) =>\n        settle(response.code ? { ok: true, response } : { ok: false, error: response }),\n      error_callback: (error) => settle({ ok: false, error }),\n    });\n\n    client.requestCode();\n  });\n}\n"],"mappings":"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"}