JS lib docs (#444)

This commit is contained in:
Zai Shi 2025-02-20 00:48:05 +01:00 committed by GitHub
parent 9a75d60f99
commit ef7e666656
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1172 additions and 180 deletions

View File

@ -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,

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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';

View File

@ -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: {

View File

@ -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";

View File

@ -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);

View 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;
}

View File

@ -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";

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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.

View 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);
}
});
```

View File

@ -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

View 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);
}
});

View 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>

View File

@ -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"
}
}

View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true
}
}

View File

@ -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",

View File

@ -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;
}
}
// ======================================================================

View File

@ -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']
],
},
};

View File

@ -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",

View File

@ -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();

View File

@ -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,

File diff suppressed because it is too large Load Diff