{"version":3,"sources":["../index.ts"],"sourcesContent":["// Minimal, reusable popup-based OAuth helper for Supabase.\n\nimport type { AuthChangeEvent, Provider, Session, SupabaseClient } from \"@supabase/supabase-js\";\n\nexport interface SupabaseSessionOptions {\n  /** OAuth provider. Default: 'google'. Example: 'google' | 'github' | 'azure' */\n  provider?: Provider;\n  /** URL the OAuth flow returns to (page should load Supabase with `detectSessionInUrl:true`). */\n  redirectTo?: string;\n  /** `window.open` features string. */\n  popupFeatures?: string;\n  /** Milliseconds before login is considered timed out. Default: 120_000. */\n  timeoutMs?: number;\n  /** Provider-specific OAuth scopes. E.g. \"email profile openid\" */\n  scopes?: string;\n  /** Extra provider query params. E.g. { access_type: \"offline\", prompt: \"consent\" } */\n  queryParams?: Record<string, string>;\n  /** Hook invoked once a session is available. */\n  onSignedIn?: (session: Session) => void;\n}\n\n/**\n * Popup OAuth and resolve to a Supabase `Session` once signed in.\n *\n * const supabase = createClient(\"https://PROJECT.supabase.co\", \"KEY\", { auth: {...} });\n * const session = await ensureSupabaseSession(supabase, { provider: \"google\" });\n *\n * How it works (high level)\n * 1) If a session already exists, return it.\n * 2) Else, ask Supabase for an OAuth URL with `{ skipBrowserRedirect: true }`.\n * 3) Try to open a popup; if blocked (no user gesture), fall back to full-page redirect.\n * 4) On completion, Supabase stores session and **broadcasts** `SIGNED_IN` to same-origin tabs.\n *    We listen for that (preferred), and also for a `postMessage` backup from the popup.\n *\n * Why backup? Broadcast can be delayed by some browser/storage quirks. The popup can call:\n *   `window.opener?.postMessage({ type: 'supabase:SIGNED_IN' }, origin)`\n * We listen for both and resolve the first that arrives.\n */\nexport async function ensureSupabaseSession(\n  client: SupabaseClient,\n  {\n    provider = \"google\" as Provider,\n    redirectTo = location.origin + location.pathname,\n    popupFeatures = \"width=480,height=640,menubar=no,toolbar=no\",\n    timeoutMs = 120_000,\n    scopes,\n    queryParams,\n    onSignedIn,\n  }: SupabaseSessionOptions = {},\n): Promise<Session> {\n  // 1) Short-circuit if already signed in\n  {\n    const { data: { session } } = await client.auth.getSession();\n    if (session) return session;\n  }\n\n  // 2) Ask Supabase for an OAuth URL (without auto-redirecting this window)\n  const { data: oauthStart, error } = await client.auth.signInWithOAuth({\n    provider,\n    options: { redirectTo, skipBrowserRedirect: true, scopes, queryParams },\n  });\n  if (error) throw error;\n  const oauthUrl = oauthStart?.url;\n  if (!oauthUrl) throw new Error(\"No OAuth URL returned\");\n\n  // 3) Try popup. If blocked, fall back to full-page redirect.\n  // Browsers block `window.open` unless triggered by a user gesture (e.g., click).\n  const popup = window.open(oauthUrl, \"supabase-oauth\", popupFeatures);\n  if (!popup) {\n    location.assign(oauthUrl);\n    return new Promise<Session>(() => {/* navigation takes over */});\n  }\n\n  // 4) Wait for SIGNED_IN via auth broadcast or postMessage backup\n  return new Promise<Session>((resolve, reject) => {\n    let settled = false;\n    const ac = new AbortController();\n\n    const settle = (fn: () => void) => {\n      if (settled) return;\n      settled = true;\n      cleanup();\n      fn();\n    };\n\n    const finalize = async () => {\n      const { data: { session } } = await client.auth.getSession();\n      if (!session) throw new Error(\"No session after sign-in\");\n      onSignedIn?.(session);\n      return session;\n    };\n\n    // Clear listeners/intervals/timeouts and close popup (prevents leaks/double-resolve)\n    const cleanup = () => {\n      ac.abort(); // removes event listeners registered with { signal }\n      try {\n        authSub?.data?.subscription?.unsubscribe?.();\n      } catch { /* noop */ }\n      clearInterval(pollClose);\n      clearTimeout(timer);\n      try {\n        popup.close();\n      } catch { /* noop */ }\n    };\n\n    // Preferred path: Supabase cross-tab auth broadcast\n    const authSub = client.auth.onAuthStateChange((evt: AuthChangeEvent) => {\n      if (evt === \"SIGNED_IN\") {\n        finalize().then((s) => settle(() => resolve(s))).catch((err) => settle(() => reject(err)));\n      }\n    });\n\n    // Backup: postMessage from popup (call this in the popup after session is set)\n    const onMessage = (ev: MessageEvent<{ type?: string; }>) => {\n      if (ev.data?.type === \"supabase:SIGNED_IN\") {\n        finalize().then((s) => settle(() => resolve(s))).catch((err) => settle(() => reject(err)));\n      }\n    };\n    window.addEventListener(\"message\", onMessage, { signal: ac.signal });\n\n    // Detect user closing the popup without logging in\n    const pollClose = window.setInterval(() => {\n      if (!popup || popup.closed) settle(() => reject(new Error(\"Login popup closed\")));\n    }, 300);\n\n    // Defensive timeout\n    const timer = window.setTimeout(() => {\n      settle(() => reject(new Error(\"Login timed out\")));\n    }, timeoutMs);\n  });\n}\n\nexport default ensureSupabaseSession;\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,0BAAAC,IAAA,eAAAC,EAAAJ,GAsCA,eAAsBG,EACpBE,EACA,CACE,SAAAC,EAAW,SACX,WAAAC,EAAa,SAAS,OAAS,SAAS,SACxC,cAAAC,EAAgB,6CAChB,UAAAC,EAAY,KACZ,OAAAC,EACA,YAAAC,EACA,WAAAC,CACF,EAA4B,CAAC,EACX,CAElB,CACE,GAAM,CAAE,KAAM,CAAE,QAAAC,CAAQ,CAAE,EAAI,MAAMR,EAAO,KAAK,WAAW,EAC3D,GAAIQ,EAAS,OAAOA,CACtB,CAGA,GAAM,CAAE,KAAMC,EAAY,MAAAC,CAAM,EAAI,MAAMV,EAAO,KAAK,gBAAgB,CACpE,SAAAC,EACA,QAAS,CAAE,WAAAC,EAAY,oBAAqB,GAAM,OAAAG,EAAQ,YAAAC,CAAY,CACxE,CAAC,EACD,GAAII,EAAO,MAAMA,EACjB,IAAMC,EAAWF,GAAY,IAC7B,GAAI,CAACE,EAAU,MAAM,IAAI,MAAM,uBAAuB,EAItD,IAAMC,EAAQ,OAAO,KAAKD,EAAU,iBAAkBR,CAAa,EACnE,OAAKS,EAME,IAAI,QAAiB,CAACC,EAASC,IAAW,CAC/C,IAAIC,EAAU,GACRC,EAAK,IAAI,gBAETC,EAAUC,GAAmB,CAC7BH,IACJA,EAAU,GACVI,EAAQ,EACRD,EAAG,EACL,EAEME,EAAW,SAAY,CAC3B,GAAM,CAAE,KAAM,CAAE,QAAAZ,CAAQ,CAAE,EAAI,MAAMR,EAAO,KAAK,WAAW,EAC3D,GAAI,CAACQ,EAAS,MAAM,IAAI,MAAM,0BAA0B,EACxD,OAAAD,IAAaC,CAAO,EACbA,CACT,EAGMW,EAAU,IAAM,CACpBH,EAAG,MAAM,EACT,GAAI,CACFK,GAAS,MAAM,cAAc,cAAc,CAC7C,MAAQ,CAAa,CACrB,cAAcC,CAAS,EACvB,aAAaC,CAAK,EAClB,GAAI,CACFX,EAAM,MAAM,CACd,MAAQ,CAAa,CACvB,EAGMS,EAAUrB,EAAO,KAAK,kBAAmBwB,GAAyB,CAClEA,IAAQ,aACVJ,EAAS,EAAE,KAAMK,GAAMR,EAAO,IAAMJ,EAAQY,CAAC,CAAC,CAAC,EAAE,MAAOC,GAAQT,EAAO,IAAMH,EAAOY,CAAG,CAAC,CAAC,CAE7F,CAAC,EAGKC,EAAaC,GAAyC,CACtDA,EAAG,MAAM,OAAS,sBACpBR,EAAS,EAAE,KAAMK,GAAMR,EAAO,IAAMJ,EAAQY,CAAC,CAAC,CAAC,EAAE,MAAOC,GAAQT,EAAO,IAAMH,EAAOY,CAAG,CAAC,CAAC,CAE7F,EACA,OAAO,iBAAiB,UAAWC,EAAW,CAAE,OAAQX,EAAG,MAAO,CAAC,EAGnE,IAAMM,EAAY,OAAO,YAAY,IAAM,EACrC,CAACV,GAASA,EAAM,SAAQK,EAAO,IAAMH,EAAO,IAAI,MAAM,oBAAoB,CAAC,CAAC,CAClF,EAAG,GAAG,EAGAS,EAAQ,OAAO,WAAW,IAAM,CACpCN,EAAO,IAAMH,EAAO,IAAI,MAAM,iBAAiB,CAAC,CAAC,CACnD,EAAGV,CAAS,CACd,CAAC,GA5DC,SAAS,OAAOO,CAAQ,EACjB,IAAI,QAAiB,IAAM,CAA4B,CAAC,EA4DnE,CAEA,IAAOd,EAAQC","names":["index_exports","__export","index_default","ensureSupabaseSession","__toCommonJS","client","provider","redirectTo","popupFeatures","timeoutMs","scopes","queryParams","onSignedIn","session","oauthStart","error","oauthUrl","popup","resolve","reject","settled","ac","settle","fn","cleanup","finalize","authSub","pollClose","timer","evt","s","err","onMessage","ev"]}