mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
JS lib docs (#444)
This commit is contained in:
parent
9a75d60f99
commit
ef7e666656
@ -4,14 +4,13 @@
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getBrowserCompatibilityReport } from "@stackframe/stack-shared/dist/utils/browser-compat";
|
||||
import { getPublicEnvVar } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { sentryBaseConfig } from "@stackframe/stack-shared/dist/utils/sentry";
|
||||
import { nicify } from "@stackframe/stack-shared/dist/utils/strings";
|
||||
|
||||
Sentry.init({
|
||||
...sentryBaseConfig,
|
||||
|
||||
dsn: getPublicEnvVar("NEXT_PUBLIC_SENTRY_DSN"),
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
enabled: process.env.NODE_ENV !== "development" && !process.env.CI,
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { checkApiKeySet } from "@/lib/api-keys";
|
||||
import { Tenancy, getSoleTenancyFromProject } from "@/lib/tenancies";
|
||||
import { getSoleTenancyFromProject } from "@/lib/tenancies";
|
||||
import { decodeAccessToken, oauthCookieSchema } from "@/lib/tokens";
|
||||
import { getProvider } from "@/oauth";
|
||||
import { prismaClient } from "@/prisma-client";
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
// The config you add here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import { getPublicEnvVar } from "@/lib/env";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getBrowserCompatibilityReport } from "@stackframe/stack-shared/dist/utils/browser-compat";
|
||||
import { getPublicEnvVar } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { sentryBaseConfig } from "@stackframe/stack-shared/dist/utils/sentry";
|
||||
import { nicify } from "@stackframe/stack-shared/dist/utils/strings";
|
||||
import posthog from "posthog-js";
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import Loading from "@/app/loading";
|
||||
import { useStackApp, useUser } from "@stackframe/stack";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
import { FormDialog } from "@/components/form-dialog";
|
||||
import { InputField, SwitchField } from "@/components/form-fields";
|
||||
import type { AdminProject } from "@stackframe/stack";
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { AdminProject } from "@stackframe/stack";
|
||||
import { yupBoolean, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { sharedProviders } from "@stackframe/stack-shared/dist/utils/oauth";
|
||||
import { ActionDialog, Badge, BrandIcons, InlineCode, Label, SimpleTooltip, Typography } from "@stackframe/stack-ui";
|
||||
import clsx from "clsx";
|
||||
|
||||
@ -4,12 +4,12 @@ import { FormDialog } from "@/components/form-dialog";
|
||||
import { InputField, SelectField } from "@/components/form-fields";
|
||||
import { useRouter } from "@/components/router";
|
||||
import { SettingCard, SettingText } from "@/components/settings";
|
||||
import { getPublicEnvVar } from "@/lib/env";
|
||||
import { AdminEmailConfig, AdminProject } from "@stackframe/stack";
|
||||
import { Reader } from "@stackframe/stack-emails/dist/editor/email-builder/index";
|
||||
import { EMAIL_TEMPLATES_METADATA, convertEmailSubjectVariables, convertEmailTemplateMetadataExampleValues, convertEmailTemplateVariables, validateEmailTemplateContent } from "@stackframe/stack-emails/dist/utils";
|
||||
import { EmailTemplateType } from "@stackframe/stack-shared/dist/interface/crud/email-templates";
|
||||
import { strictEmailSchema } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { getPublicEnvVar } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { deepPlainEquals } from "@stackframe/stack-shared/dist/utils/objects";
|
||||
import { ActionCell, ActionDialog, Alert, Button, Card, SimpleTooltip, Typography, useToast } from "@stackframe/stack-ui";
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { InputField, SwitchField } from "@/components/form-fields";
|
||||
import { StyledLink } from "@/components/link";
|
||||
import { FormSettingCard, SettingCard, SettingSwitch, SettingText } from "@/components/settings";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionDialog, Alert, Button, Typography } from "@stackframe/stack-ui";
|
||||
import * as yup from "yup";
|
||||
import { PageLayout } from "../page-layout";
|
||||
|
||||
@ -5,7 +5,7 @@ import { Link } from "@/components/link";
|
||||
import { Logo } from "@/components/logo";
|
||||
import { ProjectSwitcher } from "@/components/project-switcher";
|
||||
import ThemeToggle from "@/components/theme-toggle";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AdminProject, UserButton, useUser } from "@stackframe/stack";
|
||||
import { EMAIL_TEMPLATES_METADATA } from "@stackframe/stack-emails/dist/utils";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { SettingCard } from "@/components/settings";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { Alert, Badge, Button, CopyButton, Label, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
@ -4,8 +4,8 @@ import { FormDialog, SmartFormDialog } from "@/components/form-dialog";
|
||||
import { InputField } from "@/components/form-fields";
|
||||
import { useRouter } from "@/components/router";
|
||||
import { SettingCard } from "@/components/settings";
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { urlSchema } from "@stackframe/stack-shared/dist/schema-fields";
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { ActionCell, ActionDialog, Alert, Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Typography } from "@stackframe/stack-ui";
|
||||
import { useState } from "react";
|
||||
import { SvixProvider, useEndpoints, useSvix } from "svix-react";
|
||||
|
||||
@ -3,11 +3,11 @@ import { RouterProvider } from '@/components/router';
|
||||
import { SiteLoadingIndicatorDisplay } from '@/components/site-loading-indicator';
|
||||
import { StyleLink } from '@/components/style-link';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { stackServerApp } from '@/stack';
|
||||
import { StackProvider, StackTheme } from '@stackframe/stack';
|
||||
import { getEnvVariable, getNodeEnvironment, getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { Toaster } from '@stackframe/stack-ui';
|
||||
import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { Toaster, cn } from '@stackframe/stack-ui';
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { GeistMono } from "geist/font/mono";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client';
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { useStackApp, useUser } from '@stackframe/stack';
|
||||
import posthog from 'posthog-js';
|
||||
import { PostHogProvider } from 'posthog-js/react';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { Button, CopyField, Tabs, TabsContent, TabsList, TabsTrigger } from "@stackframe/stack-ui";
|
||||
|
||||
export default function EnvKeys(props: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { getPublicEnvVar } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useEffect, useState } from "react";
|
||||
import packageJson from "../../package.json";
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
|
||||
export function useAnimationFrame(callback: FrameRequestCallback) {
|
||||
const actualCallbackRef = useRef(callback);
|
||||
|
||||
60
apps/dashboard/src/lib/env.tsx
Normal file
60
apps/dashboard/src/lib/env.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
// hack to make sure process is defined in non-node environments
|
||||
// NEXT_LINE_PLATFORM js
|
||||
process = (globalThis as any).process ?? { env: {} };
|
||||
|
||||
const _inlineEnvVars = {
|
||||
NEXT_PUBLIC_STACK_API_URL: process.env.NEXT_PUBLIC_STACK_API_URL,
|
||||
NEXT_PUBLIC_BROWSER_STACK_API_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_API_URL,
|
||||
NEXT_PUBLIC_SERVER_STACK_API_URL: process.env.NEXT_PUBLIC_SERVER_STACK_API_URL,
|
||||
NEXT_PUBLIC_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL,
|
||||
NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL,
|
||||
NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL: process.env.NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL,
|
||||
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
|
||||
NEXT_PUBLIC_STACK_SVIX_SERVER_URL: process.env.NEXT_PUBLIC_STACK_SVIX_SERVER_URL,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: process.env.NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY,
|
||||
NEXT_PUBLIC_STACK_EMULATOR_ENABLED: process.env.NEXT_PUBLIC_STACK_EMULATOR_ENABLED,
|
||||
NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID,
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_PROJECT_ID,
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
NEXT_PUBLIC_STACK_URL: process.env.NEXT_PUBLIC_STACK_URL,
|
||||
NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: process.env.NEXT_PUBLIC_STACK_INBUCKET_WEB_URL,
|
||||
} as const;
|
||||
|
||||
// This will be replaced with the actual env vars after a docker build
|
||||
const _postBuildEnvVars = {
|
||||
NEXT_PUBLIC_STACK_API_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_API_URL",
|
||||
NEXT_PUBLIC_BROWSER_STACK_API_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_BROWSER_STACK_API_URL",
|
||||
NEXT_PUBLIC_SERVER_STACK_API_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SERVER_STACK_API_URL",
|
||||
NEXT_PUBLIC_STACK_DASHBOARD_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_DASHBOARD_URL",
|
||||
NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL",
|
||||
NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL",
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_PROJECT_ID",
|
||||
NEXT_PUBLIC_POSTHOG_KEY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_POSTHOG_KEY",
|
||||
NEXT_PUBLIC_STACK_SVIX_SERVER_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_SVIX_SERVER_URL",
|
||||
NEXT_PUBLIC_SENTRY_DSN: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SENTRY_DSN",
|
||||
NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY",
|
||||
NEXT_PUBLIC_STACK_EMULATOR_ENABLED: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_EMULATOR_ENABLED",
|
||||
NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID",
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY",
|
||||
NEXT_PUBLIC_STACK_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_URL",
|
||||
NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_INBUCKET_WEB_URL",
|
||||
} satisfies typeof _inlineEnvVars;
|
||||
|
||||
// If this is not replaced with "true", then we will not use inline env vars
|
||||
const _usePostBuildEnvVars = 'STACK_ENV_VAR_SENTINEL_USE_INLINE_ENV_VARS';
|
||||
|
||||
export function getPublicEnvVar(name: keyof typeof _inlineEnvVars) {
|
||||
// This is a hack to force the compiler not to do any smart optimizations
|
||||
const _ = _usePostBuildEnvVars.toString() + _inlineEnvVars.toString(); // Force runtime evaluation
|
||||
|
||||
const value = _usePostBuildEnvVars.slice(0) === 'true' ? _postBuildEnvVars[name] : _inlineEnvVars[name];
|
||||
|
||||
if (_usePostBuildEnvVars.slice(0) === 'true' && value && value.startsWith('STACK_ENV_VAR_SENTINEL')) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getPublicEnvVar } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { getPublicEnvVar } from "@/lib/env";
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { redirect } from "next/navigation";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import './polyfills';
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import { wait } from '@stackframe/stack-shared/dist/utils/promises';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const corsAllowedRequestHeaders = [
|
||||
// General
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { StackServerApp } from '@stackframe/stack';
|
||||
import { getPublicEnvVar } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { getPublicEnvVar } from "@/lib/env";
|
||||
import { throwErr } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
import './polyfills';
|
||||
|
||||
if (getPublicEnvVar("NEXT_PUBLIC_STACK_PROJECT_ID") !== "internal") {
|
||||
@ -7,6 +8,12 @@ if (getPublicEnvVar("NEXT_PUBLIC_STACK_PROJECT_ID") !== "internal") {
|
||||
}
|
||||
|
||||
export const stackServerApp = new StackServerApp<"nextjs-cookie", true, 'internal'>({
|
||||
baseUrl: {
|
||||
browser: getPublicEnvVar("NEXT_PUBLIC_BROWSER_STACK_API_URL") ?? getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") ?? throwErr("NEXT_PUBLIC_BROWSER_STACK_API_URL is not set"),
|
||||
server: getPublicEnvVar("NEXT_PUBLIC_SERVER_STACK_API_URL") ?? getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") ?? throwErr("NEXT_PUBLIC_SERVER_STACK_API_URL is not set"),
|
||||
},
|
||||
projectId: "internal",
|
||||
publishableClientKey: getPublicEnvVar("NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY"),
|
||||
tokenStore: "nextjs-cookie",
|
||||
urls: {
|
||||
afterSignIn: "/projects",
|
||||
|
||||
@ -16,9 +16,9 @@ export NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PU
|
||||
export STACK_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY}
|
||||
export STACK_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY}
|
||||
|
||||
export NEXT_PUBLIC_CLIENT_STACK_DASHBOARD_URL=${NEXT_PUBLIC_STACK_DASHBOARD_URL}
|
||||
export NEXT_PUBLIC_BROWSER_STACK_DASHBOARD_URL=${NEXT_PUBLIC_STACK_DASHBOARD_URL}
|
||||
export NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL="http://localhost:8101"
|
||||
export NEXT_PUBLIC_CLIENT_STACK_API_URL=${NEXT_PUBLIC_STACK_API_URL}
|
||||
export NEXT_PUBLIC_BROWSER_STACK_API_URL=${NEXT_PUBLIC_STACK_API_URL}
|
||||
export NEXT_PUBLIC_SERVER_STACK_API_URL="http://localhost:8102"
|
||||
|
||||
export USE_INLINE_ENV_VARS=true
|
||||
|
||||
@ -110,6 +110,9 @@ navigation:
|
||||
- page: Self-Hosting
|
||||
icon: fa-regular fa-house-laptop
|
||||
path: ./docs/pages/others/self-host.mdx
|
||||
- page: Vanilla JavaScript Client
|
||||
icon: fa-brands fa-js
|
||||
path: ./docs/pages/others/js-client.mdx
|
||||
- tab: components
|
||||
layout:
|
||||
- page: All Components
|
||||
|
||||
@ -5,7 +5,11 @@ subtitle: Getting started with Stack in 5 minutes
|
||||
|
||||
## Setup
|
||||
|
||||
To get started with Stack, you need a [Next.js project](https://nextjs.org/docs/getting-started/installation) with the app router. The pages router is not supported.
|
||||
<Info>
|
||||
This is the setup guide for Next.js. If you are looking for the setup guide for vanilla JavaScript, check out the [JavaScript Client Guide](../others/js-client.mdx).
|
||||
</Info>
|
||||
|
||||
To get started with Stack Auth, you need a [Next.js project](https://nextjs.org/docs/getting-started/installation) with the app router. The pages router is not supported.
|
||||
|
||||
We recommend using our **setup wizard**, which will automatically detect your project structure and guide you through the installation process. In case it fails, you can choose to do the manual installation instead.
|
||||
|
||||
|
||||
227
docs/fern/docs/pages/others/js-client.mdx
Normal file
227
docs/fern/docs/pages/others/js-client.mdx
Normal file
@ -0,0 +1,227 @@
|
||||
---
|
||||
slug: others/js-client
|
||||
subtitle: Vanilla JavaScript client library
|
||||
---
|
||||
|
||||
Stack Auth provides a vanilla JavaScript/TypeScript client library that works with any JavaScript framework in both frontend and backend environments.
|
||||
|
||||
Key differences between the JavaScript and Next.js client libraries:
|
||||
- No built-in UI components or default pages - you'll need to implement your own UI and callback handlers
|
||||
- No React hooks functionality (useUser(), useStackApp(), etc.)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @stackframe/js
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
### Initialization
|
||||
|
||||
```typescript
|
||||
import { StackServerApp } from "@stackframe/js";
|
||||
|
||||
const stackServerApp = new StackServerApp({
|
||||
projectId: "your-project-id-from-dashboard",
|
||||
publishableClientKey: "your-publishable-client-key-from-dashboard",
|
||||
secretServerKey: "your-secret-server-key-from-dashboard",
|
||||
tokenStore: "memory",
|
||||
});
|
||||
```
|
||||
|
||||
### Example usage
|
||||
|
||||
```typescript
|
||||
const user = await stackServerApp.getUser("user_id");
|
||||
|
||||
await user.update({
|
||||
displayName: "New Display Name",
|
||||
});
|
||||
|
||||
const team = await stackServerApp.createTeam({
|
||||
name: "New Team",
|
||||
});
|
||||
|
||||
await team.addUser(user.id);
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
### Initialization
|
||||
|
||||
```typescript
|
||||
import { StackClientApp } from "@stackframe/js";
|
||||
|
||||
const stackClientApp = new StackClientApp({
|
||||
projectId: "your-project-id-from-dashboard",
|
||||
publishableClientKey: "your-publishable-client-key-from-dashboard",
|
||||
tokenStore: "cookie",
|
||||
});
|
||||
```
|
||||
|
||||
### Example usage
|
||||
|
||||
```typescript
|
||||
await stackClientApp.signInWithCredential({
|
||||
email: "test@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
|
||||
const user = await stackClientApp.getUser();
|
||||
|
||||
await user.update({
|
||||
displayName: "New Display Name",
|
||||
});
|
||||
|
||||
await user.signOut();
|
||||
```
|
||||
|
||||
## HTML Example
|
||||
|
||||
Here is how to use Stack Auth with Vite, other frameworks work with the same principle. Below shows a simple sign in/sign up example. The full example is available [here](https://github.com/stack-auth/stack/tree/main/examples/js-example).
|
||||
|
||||
```html title="index.html"
|
||||
<!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 Example</title>
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stack Auth JS Example</h1>
|
||||
|
||||
<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>
|
||||
<a href="#" id="showSignUp">Create account</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="signUpForm" class="hidden">
|
||||
<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>
|
||||
<a href="#" id="showSignIn">Back to sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userInfo" class="hidden">
|
||||
<h2>User Information</h2>
|
||||
<p>Email: <span id="userEmail"></span></p>
|
||||
<button id="signOut">Sign Out</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/browser.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```typescript title="browser.ts"
|
||||
import { StackClientApp } from "@stackframe/js";
|
||||
|
||||
const stackClientApp = new StackClientApp({
|
||||
baseUrl: import.meta.env.VITE_STACK_API_URL,
|
||||
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
||||
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
tokenStore: "cookie",
|
||||
});
|
||||
|
||||
const updateUIState = (user: any | null) => {
|
||||
const loginForm = document.getElementById("loginForm");
|
||||
const signUpForm = document.getElementById("signUpForm");
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
const userEmailSpan = document.getElementById("userEmail");
|
||||
|
||||
if (user) {
|
||||
loginForm?.classList.add("hidden");
|
||||
signUpForm?.classList.add("hidden");
|
||||
userInfo?.classList.remove("hidden");
|
||||
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
||||
} else {
|
||||
loginForm?.classList.remove("hidden");
|
||||
signUpForm?.classList.add("hidden");
|
||||
userInfo?.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
|
||||
stackClientApp.getUser().then(updateUIState);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const user = await stackClientApp.getUser();
|
||||
updateUIState(user);
|
||||
passwordInput.value = "";
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign in failed. Please check your email and password and try again.");
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const user = await stackClientApp.getUser();
|
||||
updateUIState(user);
|
||||
passwordInput.value = "";
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signOut")?.addEventListener("click", async () => {
|
||||
const user = await stackClientApp.getUser();
|
||||
if (user) {
|
||||
await user.signOut();
|
||||
updateUIState(null);
|
||||
}
|
||||
});
|
||||
|
||||
```
|
||||
@ -4,3 +4,7 @@ NEXT_PUBLIC_STACK_API_URL=http://localhost:8102
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID=internal
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
|
||||
STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only
|
||||
|
||||
VITE_STACK_API_URL=http://localhost:8102
|
||||
VITE_STACK_PROJECT_ID=internal
|
||||
VITE_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
|
||||
|
||||
94
examples/js-example/browser.ts
Normal file
94
examples/js-example/browser.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { StackClientApp } from "@stackframe/js";
|
||||
|
||||
const stackClientApp = new StackClientApp({
|
||||
baseUrl: import.meta.env.VITE_STACK_API_URL,
|
||||
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
||||
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
tokenStore: "cookie",
|
||||
});
|
||||
|
||||
const updateUIState = (user: any | null) => {
|
||||
const loginForm = document.getElementById("loginForm");
|
||||
const signUpForm = document.getElementById("signUpForm");
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
const userEmailSpan = document.getElementById("userEmail");
|
||||
|
||||
if (user) {
|
||||
loginForm?.classList.add("hidden");
|
||||
signUpForm?.classList.add("hidden");
|
||||
userInfo?.classList.remove("hidden");
|
||||
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
||||
} else {
|
||||
loginForm?.classList.remove("hidden");
|
||||
signUpForm?.classList.add("hidden");
|
||||
userInfo?.classList.add("hidden");
|
||||
}
|
||||
};
|
||||
|
||||
stackClientApp.getUser().then(updateUIState);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const user = await stackClientApp.getUser();
|
||||
updateUIState(user);
|
||||
passwordInput.value = "";
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign in failed. Please check your email and password and try again.");
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
const user = await stackClientApp.getUser();
|
||||
updateUIState(user);
|
||||
passwordInput.value = "";
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signOut")?.addEventListener("click", async () => {
|
||||
const user = await stackClientApp.getUser();
|
||||
if (user) {
|
||||
await user.signOut();
|
||||
updateUIState(null);
|
||||
}
|
||||
});
|
||||
44
examples/js-example/index.html
Normal file
44
examples/js-example/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!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 Example</title>
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stack Auth JS Example</h1>
|
||||
|
||||
<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>
|
||||
<a href="#" id="showSignUp">Create account</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="signUpForm" class="hidden">
|
||||
<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>
|
||||
<a href="#" id="showSignIn">Back to sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userInfo" class="hidden">
|
||||
<h2>User Information</h2>
|
||||
<p>Email: <span id="userEmail"></span></p>
|
||||
<button id="signOut">Sign Out</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/browser.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -4,13 +4,16 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "dotenv -e .env.development -- tsx index.ts"
|
||||
"start": "dotenv -e .env.development -e .env.local -- tsx node.ts",
|
||||
"dev": "dotenv -e .env.development -e .env.local -- vite --port 8119"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@stackframe/js": "workspace:*",
|
||||
"dotenv-cli": "^7.4.1"
|
||||
"dotenv-cli": "^7.4.1",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
8
examples/js-example/tsconfig.json
Normal file
8
examples/js-example/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,6 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@simplewebauthn/browser": "^11.0.0",
|
||||
"@stackframe/stack-sc": "workspace:*",
|
||||
"@stackframe/stack-shared": "workspace:*",
|
||||
"color": "^4.2.3",
|
||||
"cookie": "^0.6.0",
|
||||
|
||||
@ -65,91 +65,3 @@ export function getNextRuntime() {
|
||||
export function getNodeEnvironment() {
|
||||
return getEnvVariable("NODE_ENV", "");
|
||||
}
|
||||
|
||||
// ===================== Hack to use dynamic env vars in docker build =====================
|
||||
|
||||
const _inlineEnvVars = {
|
||||
NEXT_PUBLIC_STACK_API_URL: {
|
||||
'default': process.env.NEXT_PUBLIC_STACK_API_URL,
|
||||
'client': process.env.NEXT_PUBLIC_CLIENT_STACK_API_URL,
|
||||
'server': process.env.NEXT_PUBLIC_SERVER_STACK_API_URL,
|
||||
},
|
||||
NEXT_PUBLIC_STACK_DASHBOARD_URL: {
|
||||
'default': process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL,
|
||||
'client': process.env.NEXT_PUBLIC_CLIENT_STACK_DASHBOARD_URL,
|
||||
'server': process.env.NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL,
|
||||
},
|
||||
NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
|
||||
NEXT_PUBLIC_STACK_SVIX_SERVER_URL: process.env.NEXT_PUBLIC_STACK_SVIX_SERVER_URL,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: process.env.NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY,
|
||||
NEXT_PUBLIC_STACK_EMULATOR_ENABLED: process.env.NEXT_PUBLIC_STACK_EMULATOR_ENABLED,
|
||||
NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID,
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID: process.env.NEXT_PUBLIC_STACK_PROJECT_ID,
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
NEXT_PUBLIC_STACK_URL: process.env.NEXT_PUBLIC_STACK_URL,
|
||||
NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: process.env.NEXT_PUBLIC_STACK_INBUCKET_WEB_URL,
|
||||
} as const;
|
||||
|
||||
// This will be replaced with the actual env vars after a docker build
|
||||
const _postBuildEnvVars = {
|
||||
NEXT_PUBLIC_STACK_API_URL: {
|
||||
'default': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_API_URL',
|
||||
'client': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_CLIENT_STACK_API_URL',
|
||||
'server': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SERVER_STACK_API_URL',
|
||||
},
|
||||
NEXT_PUBLIC_STACK_DASHBOARD_URL: {
|
||||
'default': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_DASHBOARD_URL',
|
||||
'client': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_CLIENT_STACK_DASHBOARD_URL',
|
||||
'server': 'STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SERVER_STACK_DASHBOARD_URL',
|
||||
},
|
||||
NEXT_PUBLIC_STACK_PROJECT_ID: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_PROJECT_ID",
|
||||
NEXT_PUBLIC_POSTHOG_KEY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_POSTHOG_KEY",
|
||||
NEXT_PUBLIC_STACK_SVIX_SERVER_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_SVIX_SERVER_URL",
|
||||
NEXT_PUBLIC_SENTRY_DSN: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_SENTRY_DSN",
|
||||
NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_VERSION_ALERTER_SEVERE_ONLY",
|
||||
NEXT_PUBLIC_STACK_EMULATOR_ENABLED: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_EMULATOR_ENABLED",
|
||||
NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID",
|
||||
NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY",
|
||||
NEXT_PUBLIC_STACK_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_URL",
|
||||
NEXT_PUBLIC_STACK_INBUCKET_WEB_URL: "STACK_ENV_VAR_SENTINEL_NEXT_PUBLIC_STACK_INBUCKET_WEB_URL",
|
||||
} satisfies typeof _inlineEnvVars;
|
||||
|
||||
// If this is not replaced with "true", then we will not use inline env vars
|
||||
const _usePostBuildEnvVars = 'STACK_ENV_VAR_SENTINEL_USE_INLINE_ENV_VARS';
|
||||
|
||||
export function getPublicEnvVar(name: keyof typeof _inlineEnvVars) {
|
||||
// This is a hack to force the compiler not to do any smart optimizations
|
||||
const _ = _usePostBuildEnvVars.toString() + _inlineEnvVars.toString(); // Force runtime evaluation
|
||||
|
||||
const value = _usePostBuildEnvVars.slice(0) === 'true' ? _postBuildEnvVars[name] : _inlineEnvVars[name];
|
||||
|
||||
// Helper function to check if a value is a sentinel
|
||||
const isSentinel = (str?: string) => {
|
||||
return _usePostBuildEnvVars.slice(0) === 'true' && str && str.startsWith('STACK_ENV_VAR_SENTINEL');
|
||||
};
|
||||
|
||||
// If it's a dictionary with client/server values
|
||||
if (typeof value === 'object') {
|
||||
const preferredValue = isBrowserLike() ? value.client : value.server;
|
||||
|
||||
// Check for sentinel values
|
||||
if (isSentinel(preferredValue)) {
|
||||
return isSentinel(value.default) ? undefined : value.default;
|
||||
}
|
||||
if (isSentinel(value.default)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return preferredValue || value.default;
|
||||
} else if (typeof value === 'string') {
|
||||
if (isSentinel(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const defaults = require("../../eslint-configs/defaults.js");
|
||||
const publicVars = require("../../eslint-configs/extra-rules.js");
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
@ -9,7 +8,6 @@ module.exports = {
|
||||
"rules": {
|
||||
"no-restricted-syntax": [
|
||||
...defaults.rules["no-restricted-syntax"],
|
||||
publicVars['no-next-public-env']
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@simplewebauthn/browser": "^11.0.0",
|
||||
"//": "NEXT_LINE_PLATFORM react-like",
|
||||
"@stackframe/stack-sc": "workspace:*",
|
||||
"@stackframe/stack-shared": "workspace:*",
|
||||
"//": "NEXT_LINE_PLATFORM react-like",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// NEXT_LINE_PLATFORM react-like
|
||||
import { cookies as rscCookies, headers as rscHeaders } from '@stackframe/stack-sc/force-react-server';
|
||||
import { isBrowserLike } from '@stackframe/stack-shared/dist/utils/env';
|
||||
import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors';
|
||||
@ -39,10 +40,14 @@ export async function createCookieHelper(): Promise<CookieHelper> {
|
||||
if (isBrowserLike()) {
|
||||
return createBrowserCookieHelper();
|
||||
} else {
|
||||
// IF_PLATFORM react-like
|
||||
return createNextCookieHelper(
|
||||
await rscCookies(),
|
||||
await rscHeaders(),
|
||||
);
|
||||
// ELSE_PLATFORM
|
||||
return await createEmptyCookieHelper();
|
||||
// END_PLATFORM
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +72,7 @@ function handleCookieError(e: unknown, options: DeleteCookieOptions | SetCookieO
|
||||
}
|
||||
}
|
||||
|
||||
// IF_PLATFORM react-like
|
||||
function createNextCookieHelper(
|
||||
rscCookiesAwaited: Awaited<ReturnType<typeof rscCookies>>,
|
||||
rscHeadersAwaited: Awaited<ReturnType<typeof rscHeaders>>,
|
||||
@ -136,6 +142,7 @@ function createNextCookieHelper(
|
||||
};
|
||||
return cookieHelper;
|
||||
}
|
||||
// END_PLATFORM
|
||||
|
||||
export function getCookieClient(name: string): string | null {
|
||||
ensureClient();
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
|
||||
import { isReactServer } from "@stackframe/stack-sc";
|
||||
import { KnownErrors, StackAdminInterface, StackClientInterface, StackServerInterface } from "@stackframe/stack-shared";
|
||||
import { ProductionModeError, getProductionModeErrors } from "@stackframe/stack-shared/dist/helpers/production-mode";
|
||||
import { ApiKeyCreateCrudRequest, ApiKeyCreateCrudResponse } from "@stackframe/stack-shared/dist/interface/adminInterface";
|
||||
@ -17,7 +16,7 @@ import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
|
||||
import { encodeBase64 } from "@stackframe/stack-shared/dist/utils/bytes";
|
||||
import { AsyncCache } from "@stackframe/stack-shared/dist/utils/caches";
|
||||
import { scrambleDuringCompileTime } from "@stackframe/stack-shared/dist/utils/compile-time";
|
||||
import { getPublicEnvVar, isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
|
||||
import { StackAssertionError, concatStacktraces, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
|
||||
import { ReadonlyJson } from "@stackframe/stack-shared/dist/utils/json";
|
||||
import { DependenciesMap } from "@stackframe/stack-shared/dist/utils/maps";
|
||||
@ -37,11 +36,22 @@ import React, { useCallback, useMemo } from "react";
|
||||
import { constructRedirectUrl } from "../utils/url";
|
||||
import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "./auth";
|
||||
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createEmptyCookieHelper, deleteCookieClient, getCookieClient, setOrDeleteCookie, setOrDeleteCookieClient } from "./cookie";
|
||||
|
||||
let isReactServer = false;
|
||||
// IF_PLATFORM react-like
|
||||
import * as sc from "@stackframe/stack-sc";
|
||||
isReactServer = sc.isReactServer;
|
||||
// END_PLATFORM
|
||||
|
||||
// NextNavigation.useRouter does not exist in react-server environments and some bundlers try to be helpful and throw a warning. Ignore the warning.
|
||||
const NextNavigation = scrambleDuringCompileTime(NextNavigationUnscrambled);
|
||||
|
||||
const clientVersion = process.env.STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION ?? throwErr("Missing STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION. This should be a compile-time variable set by Stack's build system.");
|
||||
|
||||
// hack to make sure process is defined in non-node environments
|
||||
// NEXT_LINE_PLATFORM js
|
||||
process = (globalThis as any).process ?? { env: {} };
|
||||
|
||||
type RequestLike = {
|
||||
headers: {
|
||||
get: (name: string) => string | null,
|
||||
@ -114,11 +124,11 @@ function getUrls(partial: Partial<HandlerUrls>): HandlerUrls {
|
||||
}
|
||||
|
||||
function getDefaultProjectId() {
|
||||
return getPublicEnvVar("NEXT_PUBLIC_STACK_PROJECT_ID") || throwErr(new Error("Welcome to Stack Auth! It seems that you haven't provided a project ID. Please create a project on the Stack dashboard at https://app.stack-auth.com and put it in the NEXT_PUBLIC_STACK_PROJECT_ID environment variable."));
|
||||
return process.env.NEXT_PUBLIC_STACK_PROJECT_ID || throwErr(new Error("Welcome to Stack Auth! It seems that you haven't provided a project ID. Please create a project on the Stack dashboard at https://app.stack-auth.com and put it in the NEXT_PUBLIC_STACK_PROJECT_ID environment variable."));
|
||||
}
|
||||
|
||||
function getDefaultPublishableClientKey() {
|
||||
return getPublicEnvVar("NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY") || throwErr(new Error("Welcome to Stack Auth! It seems that you haven't provided a publishable client key. Please create an API key for your project on the Stack dashboard at https://app.stack-auth.com and copy your publishable client key into the NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY environment variable."));
|
||||
return process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY || throwErr(new Error("Welcome to Stack Auth! It seems that you haven't provided a publishable client key. Please create an API key for your project on the Stack dashboard at https://app.stack-auth.com and copy your publishable client key into the NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY environment variable."));
|
||||
}
|
||||
|
||||
function getDefaultSecretServerKey() {
|
||||
@ -129,13 +139,50 @@ function getDefaultSuperSecretAdminKey() {
|
||||
return process.env.STACK_SUPER_SECRET_ADMIN_KEY || throwErr(new Error("No super secret admin key provided. Please copy your key from the Stack dashboard and put it in the STACK_SUPER_SECRET_ADMIN_KEY environment variable."));
|
||||
}
|
||||
|
||||
function getDefaultBaseUrl() {
|
||||
const url = getPublicEnvVar("NEXT_PUBLIC_STACK_API_URL") || getPublicEnvVar("NEXT_PUBLIC_STACK_URL") || defaultBaseUrl;
|
||||
/**
|
||||
* Returns the base URL for the Stack API.
|
||||
*
|
||||
* The URL can be specified in several ways, in order of precedence:
|
||||
* 1. Directly through userSpecifiedBaseUrl parameter as string or browser/server object
|
||||
* 2. Through environment variables:
|
||||
* - Browser: NEXT_PUBLIC_BROWSER_STACK_API_URL
|
||||
* - Server: NEXT_PUBLIC_SERVER_STACK_API_URL
|
||||
* - Fallback: NEXT_PUBLIC_STACK_API_URL or NEXT_PUBLIC_STACK_URL
|
||||
* 3. Default base URL if none of the above are specified
|
||||
*
|
||||
* The function also ensures the URL doesn't end with a trailing slash
|
||||
* by removing it if present.
|
||||
*
|
||||
* @param userSpecifiedBaseUrl - Optional URL override as string or {browser, server} object
|
||||
* @returns The configured base URL without trailing slash
|
||||
|
||||
*/
|
||||
function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined) {
|
||||
let url;
|
||||
if (userSpecifiedBaseUrl) {
|
||||
if (typeof userSpecifiedBaseUrl === "string") {
|
||||
url = userSpecifiedBaseUrl;
|
||||
} else {
|
||||
if (isBrowserLike()) {
|
||||
url = userSpecifiedBaseUrl.browser;
|
||||
} else {
|
||||
url = userSpecifiedBaseUrl.server;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isBrowserLike()) {
|
||||
url = process.env.NEXT_PUBLIC_BROWSER_STACK_API_URL;
|
||||
} else {
|
||||
url = process.env.NEXT_PUBLIC_SERVER_STACK_API_URL;
|
||||
}
|
||||
url = url || process.env.NEXT_PUBLIC_STACK_API_URL || process.env.NEXT_PUBLIC_STACK_URL || defaultBaseUrl;
|
||||
}
|
||||
|
||||
return url.endsWith('/') ? url.slice(0, -1) : url;
|
||||
}
|
||||
|
||||
export type StackClientAppConstructorOptions<HasTokenStore extends boolean, ProjectId extends string> = {
|
||||
baseUrl?: string,
|
||||
baseUrl?: string | { browser: string, server: string },
|
||||
projectId?: ProjectId,
|
||||
publishableClientKey?: string,
|
||||
urls?: Partial<HandlerUrls>,
|
||||
@ -446,7 +493,7 @@ class _StackClientAppImpl<HasTokenStore extends boolean, ProjectId extends strin
|
||||
this._interface = _options.interface;
|
||||
} else {
|
||||
this._interface = new StackClientInterface({
|
||||
getBaseUrl: () => _options.baseUrl ?? getDefaultBaseUrl(),
|
||||
getBaseUrl: () => getBaseUrl(_options.baseUrl),
|
||||
projectId: _options.projectId ?? getDefaultProjectId(),
|
||||
clientVersion,
|
||||
publishableClientKey: _options.publishableClientKey ?? getDefaultPublishableClientKey(),
|
||||
@ -1888,12 +1935,15 @@ class _StackServerAppImpl<HasTokenStore extends boolean, ProjectId extends strin
|
||||
oauthScopesOnSignIn: options.oauthScopesOnSignIn,
|
||||
} : {
|
||||
interface: new StackServerInterface({
|
||||
getBaseUrl: () => options.baseUrl ?? getDefaultBaseUrl(),
|
||||
getBaseUrl: () => getBaseUrl(options.baseUrl),
|
||||
projectId: options.projectId ?? getDefaultProjectId(),
|
||||
clientVersion,
|
||||
publishableClientKey: options.publishableClientKey ?? getDefaultPublishableClientKey(),
|
||||
secretServerKey: options.secretServerKey ?? getDefaultSecretServerKey(),
|
||||
}),
|
||||
baseUrl: options.baseUrl,
|
||||
projectId: options.projectId,
|
||||
publishableClientKey: options.publishableClientKey,
|
||||
tokenStore: options.tokenStore,
|
||||
urls: options.urls ?? {},
|
||||
oauthScopesOnSignIn: options.oauthScopesOnSignIn ?? {},
|
||||
@ -2377,7 +2427,7 @@ class _StackAdminAppImpl<HasTokenStore extends boolean, ProjectId extends string
|
||||
constructor(options: StackAdminAppConstructorOptions<HasTokenStore, ProjectId>) {
|
||||
super({
|
||||
interface: new StackAdminInterface({
|
||||
getBaseUrl: () => options.baseUrl ?? getDefaultBaseUrl(),
|
||||
getBaseUrl: () => getBaseUrl(options.baseUrl),
|
||||
projectId: options.projectId ?? getDefaultProjectId(),
|
||||
clientVersion,
|
||||
..."projectOwnerSession" in options ? {
|
||||
@ -2388,6 +2438,8 @@ class _StackAdminAppImpl<HasTokenStore extends boolean, ProjectId extends string
|
||||
superSecretAdminKey: options.superSecretAdminKey ?? getDefaultSuperSecretAdminKey(),
|
||||
},
|
||||
}),
|
||||
baseUrl: options.baseUrl,
|
||||
projectId: options.projectId,
|
||||
tokenStore: options.tokenStore,
|
||||
urls: options.urls,
|
||||
oauthScopesOnSignIn: options.oauthScopesOnSignIn,
|
||||
|
||||
672
pnpm-lock.yaml
672
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user