mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Some checks failed
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
## Summary
Completes the env-var side of the Hexclave rebrand: every
`STACK_*`-prefixed variable (including `NEXT_PUBLIC_STACK_*` and
`VITE_STACK_*`) is renamed to `HEXCLAVE_*` across all checked-in `.env`,
`.env.development`, and `.env.example` files (30 files, ~135 keys).
Legacy `STACK_*` names keep working everywhere via dual-read, so
**existing deployments, `.env.local` files, and self-hosted setups need
no immediate migration**.
## How legacy names keep working
- **Server code** already resolves `HEXCLAVE_*` first with `STACK_*`
fallback via `getEnvVariable`. Direct `process.env.STACK_X` readers fed
by the renamed files (prisma seed, e2e tests/helpers, internal-tool
scripts, examples, `prisma.config.ts`) now read `HEXCLAVE_X || STACK_X`.
- **Client code** (Next.js build-time inlining) uses literal dual-read
expressions; the dashboard's `_inlineEnvVars` already had them.
- **Docker/self-hosting**: `docker/server/entrypoint.sh` (shared by the
server and local-emulator images) gets a generic two-way
`HEXCLAVE_`↔`STACK_` env mirror — runs at startup and again before
sentinel replacement — replacing the previous URL-trio-only mirror.
Operators can use either prefix.
## The empty-placeholder trap (`||` vs `??`)
The checked-in templates define empty placeholders (`HEXCLAVE_X=#
comment` parses to `""` via dotenv). With `?? `-based fallbacks, that
empty string would silently shadow a real value under the legacy name —
including legacy vars set in Vercel/CI env at build time, since the
tracked `.env` is present during builds. All fallback chains therefore
treat empty-as-unset (`||`):
- `getEnvVariable` and `getProcessEnv` in `packages/shared`
- the dashboard/docs/example literal dual-reads
- the generated SDK env getters (via
`packages/template/scripts/generate-env.ts`; the generated
`src/generated/env.ts` files are gitignored and regenerate at build)
## Other notable changes
- Tests that override env now set the canonical `HEXCLAVE_*` name (it
wins over `STACK_*`): e2e `cross-domain-auth`, backend
`internal-feedback-emails` in-source test.
- e2e `helpers.ts` port-prefix expansion loop also matches the
`HEXCLAVE_` prefixes.
- `docker/local-emulator/generate-env-development.mjs` reads source keys
canonically (legacy fallback) and emits canonical keys; regenerated
output matches.
- `rotate-secrets.sh` falls back to
`HEXCLAVE_DATABASE_CONNECTION_STRING`.
- Docs code snippets (`docs/code-examples`) renamed outright to
canonical names, consistent with #1571.
- OAuth callback `console.warn` in `packages/template/src/lib/auth.ts`
now says Hexclave.
## Migration note for the team
Local `.env.local` files with legacy `STACK_*` overrides keep working
**unless** the override targets a var that `.env.development` now sets
to a real (non-empty) `HEXCLAVE_*` value — the canonical name wins over
file precedence. Rename those keys in your `.env.local` once.
## Verification
- `typecheck` + `lint` pass on every touched package (shared, backend,
dashboard, e2e, internal-tool, cli, docs, template). Pre-existing
failures on dev (`admin-app-impl.ts` typecheck, dashboard metrics-page
errors) are unchanged (identical error counts with/without this change).
- `getEnvVariable`/`getProcessEnv` fallback semantics smoke-tested
directly (empty-HEXCLAVE → legacy fallback, HEXCLAVE wins when set,
defaults intact).
- `internal-feedback-emails` in-source vitest passes; emulator env
generator `--check` passes; `bash -n` on touched shell scripts.
- Two independent review agents audited the diff for correctness bugs
and coverage gaps; all confirmed findings are fixed in the third commit.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Renamed all `STACK_*` env vars (including
`NEXT_PUBLIC_STACK_*`/`VITE_STACK_*`) to `HEXCLAVE_*` across env
templates and code, with dual‑read that treats empty as unset, detects
conflicts, ignores post‑build sentinels, and falls back to legacy names.
All GitHub Actions now use `HEXCLAVE_*`; local‑emulator e2e is fixed by
setting `NEXT_PUBLIC_HEXCLAVE_IS_LOCAL_EMULATOR` in CI.
- **Refactors**
- Added conflict‑aware dual‑read helpers (prefer `HEXCLAVE_*`,
empty‑as‑unset, ignore post‑build sentinels, preserve empty passthrough)
and used them across `packages/shared` (resolver + tests),
`apps/dashboard` inline/public envs (with tests), `apps/backend` Prisma
config/seed and vitest (accept both prefixes), `packages/cli`
(API/Dashboard URLs, project ID, `HEXCLAVE_EMULATOR_HOME`; tests),
Docker (`entrypoint.sh` mirroring + `rotate-secrets.sh` DB URL),
docs/components (`docs/src/lib/env.ts`), and examples; hosted/Vite apps
now error if both spellings differ.
- Port‑prefix expansion includes `HEXCLAVE_*`; backend tests use a new
helper to resolve DB connection strings; Prisma prefers
`HEXCLAVE_DATABASE_CONNECTION_STRING` with legacy fallback.
- Generated SDK env getters use plain `HEXCLAVE_*` || `STACK_*` (no
conflict throw); dashboard inline resolver preserves empty/sentinel
passthrough to avoid build failures; docs/examples include dual‑read
utilities.
- Tests now stub canonical `HEXCLAVE_*` flags (e.g., plan limits, bot
challenge, OAuth tokens, hosted handler) to avoid shadowing/conflict
with committed defaults.
- **Migration**
- No immediate action; legacy `STACK_*` names still work.
- If both names are set with different values, builds/scripts error. Set
only `HEXCLAVE_*` or make both equal.
- SDK consumers won’t see conflict throws; update env names to
`HEXCLAVE_*` over time.
<sup>Written for commit 7539fb9fbf.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1588?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Chores**
* Migrated environment variable names from the legacy `STACK_*` prefix
to the new `HEXCLAVE_*` prefix across backend, dashboard, tooling,
Docker, and examples.
* Updated environment/config resolution to prefer `HEXCLAVE_*`, treat
empty strings as unset, and detect conflicts when both `STACK_*` and
`HEXCLAVE_*` are set to different values.
* Updated local emulator, server startup, and env-generation workflows
to use the new names (with legacy fallback where applicable).
* **Documentation**
* Updated docs and code examples to reference `HEXCLAVE_*` variables.
* **Tests**
* Refreshed unit and e2e coverage to validate dual-read behavior,
conflict detection, and empty-value handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
446 lines
12 KiB
TypeScript
446 lines
12 KiB
TypeScript
import { CodeExample } from '../lib/code-examples';
|
|
|
|
export const viteExamples = {
|
|
'vite-example': {
|
|
'init-app': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
code: `import { StackClientApp } from "@stackframe/js";
|
|
|
|
// Add type declaration for Vite's import.meta.env
|
|
declare global {
|
|
interface ImportMeta {
|
|
env: {
|
|
VITE_HEXCLAVE_API_URL: string;
|
|
VITE_HEXCLAVE_PROJECT_ID: string;
|
|
VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY: string;
|
|
};
|
|
}
|
|
}
|
|
|
|
export const stackClientApp = new StackClientApp({
|
|
baseUrl: import.meta.env.VITE_HEXCLAVE_API_URL,
|
|
projectId: import.meta.env.VITE_HEXCLAVE_PROJECT_ID,
|
|
publishableClientKey: import.meta.env.VITE_HEXCLAVE_PUBLISHABLE_CLIENT_KEY,
|
|
tokenStore: "cookie",
|
|
urls: {
|
|
oauthCallback: window.location.origin + "/oauth",
|
|
},
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'stack/client.ts'
|
|
}
|
|
] as CodeExample[],
|
|
|
|
'index-page': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'html',
|
|
code: `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Stack Auth JS Examples</title>
|
|
</head>
|
|
<body>
|
|
<h1>Stack Auth JS Examples</h1>
|
|
|
|
<div id="authOptions">
|
|
<p>Choose an authentication example:</p>
|
|
<ul>
|
|
<li><a href="/password-sign-in">Sign in with Password</a></li>
|
|
<li><a href="/password-sign-up">Create Account with Password</a></li>
|
|
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
|
|
<li><a href="/oauth">Sign in with Google</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div id="userInfo" style="display: none;">
|
|
<h2>User Information</h2>
|
|
<p>Email: <span id="userEmail"></span></p>
|
|
<button id="signOut">Sign Out</button>
|
|
</div>
|
|
|
|
<script type="module" src="/index-script.ts"></script>
|
|
</body>
|
|
</html>`,
|
|
highlightLanguage: 'html',
|
|
filename: 'index.html'
|
|
},
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'script',
|
|
code: `import { stackClientApp } from "./stack/client";
|
|
|
|
const updateUIState = (user: any | null) => {
|
|
const authOptions = document.getElementById("authOptions");
|
|
const userInfo = document.getElementById("userInfo");
|
|
const userEmailSpan = document.getElementById("userEmail");
|
|
|
|
if (user) {
|
|
if (authOptions) authOptions.style.display = "none";
|
|
if (userInfo) userInfo.style.display = "block";
|
|
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
|
} else {
|
|
if (authOptions) authOptions.style.display = "block";
|
|
if (userInfo) userInfo.style.display = "none";
|
|
}
|
|
};
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then(updateUIState);
|
|
|
|
// Handle Sign Out
|
|
document.getElementById("signOut")?.addEventListener("click", async () => {
|
|
const user = await stackClientApp.getUser();
|
|
if (user) {
|
|
await user.signOut();
|
|
updateUIState(null);
|
|
}
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'index-script.ts'
|
|
}
|
|
] as CodeExample[],
|
|
|
|
'password-sign-in': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'html',
|
|
code: `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Password Sign In</title>
|
|
</head>
|
|
<body>
|
|
<h1>Password Sign In</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="loginForm">
|
|
<h2>Sign In</h2>
|
|
<input type="email" id="emailInput" placeholder="Email" />
|
|
<input type="password" id="passwordInput" placeholder="Password" />
|
|
<button id="signIn">Sign In</button>
|
|
<div>
|
|
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/password-sign-in-script.ts"></script>
|
|
</body>
|
|
</html>`,
|
|
highlightLanguage: 'html',
|
|
filename: 'password-sign-in.html'
|
|
},
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'script',
|
|
code: `import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
document.getElementById("loginForm")?.classList.add("hidden");
|
|
document.getElementById("signUpForm")?.classList.remove("hidden");
|
|
});
|
|
|
|
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
document.getElementById("loginForm")?.classList.remove("hidden");
|
|
document.getElementById("signUpForm")?.classList.add("hidden");
|
|
});
|
|
|
|
document.getElementById("signIn")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign in failed. Please check your email and password and try again.");
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("signUp")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signUpWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign up failed. Please try again.");
|
|
return;
|
|
}
|
|
|
|
const signInResult = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (signInResult.status === "error") {
|
|
alert("Account created but sign in failed. Please sign in manually.");
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'password-sign-in-script.ts'
|
|
}
|
|
] as CodeExample[],
|
|
|
|
'password-sign-up': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'html',
|
|
code: `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Password Sign Up</title>
|
|
</head>
|
|
<body>
|
|
<h1>Password Sign Up</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="signUpForm">
|
|
<h2>Sign Up</h2>
|
|
<input type="email" id="signUpEmail" placeholder="Email" />
|
|
<input type="password" id="signUpPassword" placeholder="Password" />
|
|
<button id="signUp">Sign Up</button>
|
|
<div>
|
|
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/password-sign-up-script.ts"></script>
|
|
</body>
|
|
</html>`,
|
|
highlightLanguage: 'html',
|
|
filename: 'password-sign-up.html'
|
|
},
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'script',
|
|
code: `import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("signUp")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
|
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signUpWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Sign up failed. Please try again.");
|
|
return;
|
|
}
|
|
|
|
const signInResult = await stackClientApp.signInWithCredential({
|
|
email: emailInput.value,
|
|
password: passwordInput.value,
|
|
});
|
|
|
|
if (signInResult.status === "error") {
|
|
alert("Account created but sign in failed. Please sign in manually.");
|
|
window.location.href = "/password-sign-in";
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'password-sign-up-script.ts'
|
|
}
|
|
] as CodeExample[],
|
|
|
|
'otp-sign-in': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'html',
|
|
code: `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OTP Sign In</title>
|
|
</head>
|
|
<body>
|
|
<h1>OTP Sign In</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="otpForm">
|
|
<h2>Sign In with Email Code</h2>
|
|
<div id="emailStep">
|
|
<input type="email" id="emailInput" placeholder="Email" />
|
|
<button id="sendCode">Send Code</button>
|
|
</div>
|
|
|
|
<div id="codeStep" style="display: none;">
|
|
<p>Enter the code sent to your email</p>
|
|
<input type="text" id="codeInput" placeholder="Enter code" />
|
|
<button id="verifyCode">Verify Code</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module" src="/otp-sign-in-script.ts"></script>
|
|
</body>
|
|
</html>`,
|
|
highlightLanguage: 'html',
|
|
filename: 'otp-sign-in.html'
|
|
},
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'script',
|
|
code: `import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
document.getElementById("sendCode")?.addEventListener("click", async () => {
|
|
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
|
|
|
await stackClientApp.sendMagicLinkEmail({
|
|
email: emailInput.value,
|
|
});
|
|
|
|
document.getElementById("emailStep")!.style.display = "none";
|
|
document.getElementById("codeStep")!.style.display = "block";
|
|
});
|
|
|
|
document.getElementById("verifyCode")?.addEventListener("click", async () => {
|
|
const codeInput = document.getElementById("codeInput") as HTMLInputElement;
|
|
|
|
const result = await stackClientApp.signInWithMagicLink({
|
|
code: codeInput.value,
|
|
});
|
|
|
|
if (result.status === "error") {
|
|
alert("Verification failed. Please check the code and try again.");
|
|
} else {
|
|
window.location.href = "/";
|
|
}
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'otp-sign-in-script.ts'
|
|
}
|
|
] as CodeExample[],
|
|
|
|
'oauth': [
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'html',
|
|
code: `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OAuth Authentication</title>
|
|
<style>
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>OAuth Authentication</h1>
|
|
<p><a href="/">← Back to home</a></p>
|
|
|
|
<div id="loginButtons">
|
|
<h2>Sign In with OAuth</h2>
|
|
<button id="googleSignIn">Sign in with Google</button>
|
|
</div>
|
|
|
|
<script type="module" src="/oauth-script.ts"></script>
|
|
</body>
|
|
</html>`,
|
|
highlightLanguage: 'html',
|
|
filename: 'oauth.html'
|
|
},
|
|
{
|
|
language: 'JavaScript',
|
|
framework: 'Vanilla JavaScript',
|
|
variant: 'script',
|
|
code: `import { stackClientApp } from "./stack/client";
|
|
|
|
// Check if user is already signed in
|
|
stackClientApp.getUser().then((user) => {
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
});
|
|
|
|
// Handle Google Sign In
|
|
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
|
|
try {
|
|
await stackClientApp.signInWithOAuth('google');
|
|
} catch (error) {
|
|
console.error("Google sign in failed:", error);
|
|
alert("Failed to initialize Google sign in");
|
|
}
|
|
});
|
|
|
|
// Handle OAuth redirect
|
|
window.addEventListener("load", async () => {
|
|
try {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const code = params.get("code");
|
|
const state = params.get("state");
|
|
|
|
if (code && state) {
|
|
const user = await stackClientApp.callOAuthCallback();
|
|
if (user) {
|
|
window.location.href = "/";
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to handle OAuth redirect:", error);
|
|
alert("Authentication failed. Please try again.");
|
|
}
|
|
});`,
|
|
highlightLanguage: 'typescript',
|
|
filename: 'oauth-script.ts'
|
|
}
|
|
] as CodeExample[],
|
|
}
|
|
};
|