| 1 | --- |
| 2 | import Auth from "../../layouts/Auth.astro"; |
| 3 | --- |
| 4 | |
| 5 | <Auth |
| 6 | title="Complete Authentication" |
| 7 | message="Trying to complete the OAuth flow" |
| 8 | > |
| 9 | <section class="relay" aria-live="polite"> |
| 10 | <p id="relay-connecting">Connecting to your local Alchemy process…</p> |
| 11 | <div id="relay-manual" hidden> |
| 12 | <code id="relay-code"></code> |
| 13 | <div class="relay__actions"> |
| 14 | <button id="relay-copy" type="button">Copy code</button> |
| 15 | <a id="relay-local" href="#">Try local callback anyway</a> |
| 16 | </div> |
| 17 | </div> |
| 18 | </section> |
| 19 | </Auth> |
| 20 | |
| 21 | <script> |
| 22 | const params = new URLSearchParams(window.location.search); |
| 23 | const code = params.get("code"); |
| 24 | const state = params.get("state"); |
| 25 | const error = params.get("error"); |
| 26 | const localCallback = new URL("http://localhost:9976/auth/callback"); |
| 27 | localCallback.search = params.toString(); |
| 28 | |
| 29 | const connecting = document.querySelector<HTMLElement>("#relay-connecting")!; |
| 30 | const manual = document.querySelector<HTMLElement>("#relay-manual")!; |
| 31 | const codeBox = document.querySelector<HTMLElement>("#relay-code")!; |
| 32 | const copyButton = document.querySelector<HTMLButtonElement>("#relay-copy")!; |
| 33 | const localLink = document.querySelector<HTMLAnchorElement>("#relay-local")!; |
| 34 | |
| 35 | localLink.href = localCallback.toString(); |
| 36 | |
| 37 | const showManual = () => { |
| 38 | connecting.textContent = error |
| 39 | ? "Cloudflare did not authorize the request." |
| 40 | : "Couldn't connect to your local Alchemy process."; |
| 41 | if (!code || !state) return; |
| 42 | codeBox.textContent = `${code}#${state}`; |
| 43 | manual.hidden = false; |
| 44 | }; |
| 45 | |
| 46 | if (error || !code || !state) { |
| 47 | showManual(); |
| 48 | } else { |
| 49 | const controller = new AbortController(); |
| 50 | const timeout = window.setTimeout(() => controller.abort(), 10000); |
| 51 | |
| 52 | fetch("http://localhost:9976/auth/ping", { |
| 53 | mode: "cors", |
| 54 | cache: "no-store", |
| 55 | signal: controller.signal, |
| 56 | }) |
| 57 | .then(async (response) => { |
| 58 | if (!response.ok) throw new Error("Local callback unavailable"); |
| 59 | window.location.replace(localCallback); |
| 60 | }) |
| 61 | .catch(showManual) |
| 62 | .finally(() => window.clearTimeout(timeout)); |
| 63 | } |
| 64 | |
| 65 | copyButton.addEventListener("click", async () => { |
| 66 | await navigator.clipboard.writeText(codeBox.textContent ?? ""); |
| 67 | copyButton.textContent = "Copied"; |
| 68 | }); |
| 69 | </script> |
| 70 | |
| 71 | <style> |
| 72 | .relay { |
| 73 | width: 100%; |
| 74 | color: var(--alc-fg-2); |
| 75 | } |
| 76 | .relay p { |
| 77 | line-height: 1.6; |
| 78 | } |
| 79 | .relay code { |
| 80 | display: block; |
| 81 | overflow-wrap: anywhere; |
| 82 | padding: 1rem; |
| 83 | border: 1px solid var(--alc-hairline-2); |
| 84 | border-radius: 0.5rem; |
| 85 | background: var(--alc-bg-elev-1); |
| 86 | color: var(--alc-fg-1); |
| 87 | text-align: left; |
| 88 | user-select: all; |
| 89 | } |
| 90 | .relay__actions { |
| 91 | display: flex; |
| 92 | justify-content: center; |
| 93 | gap: 0.75rem; |
| 94 | margin-top: 1rem; |
| 95 | } |
| 96 | .relay__actions button, |
| 97 | .relay__actions a { |
| 98 | padding: 0.625rem 1rem; |
| 99 | border: 1px solid var(--alc-hairline-2); |
| 100 | border-radius: 0.5rem; |
| 101 | background: var(--alc-bg-elev-1); |
| 102 | color: var(--alc-fg-1); |
| 103 | font: inherit; |
| 104 | text-decoration: none; |
| 105 | cursor: pointer; |
| 106 | } |
| 107 | </style> |