From 016cd4ca24cf040436eaa322efcea5290df5de9e Mon Sep 17 00:00:00 2001 From: Brion Date: Sun, 5 Jul 2026 14:54:10 +0530 Subject: [PATCH 1/3] Remove `internal_login` scope --- packages/javascript/src/constants/OIDCRequestConstants.ts | 2 +- packages/javascript/src/constants/ScopeConstants.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/javascript/src/constants/OIDCRequestConstants.ts b/packages/javascript/src/constants/OIDCRequestConstants.ts index fc53f33..861e3ef 100644 --- a/packages/javascript/src/constants/OIDCRequestConstants.ts +++ b/packages/javascript/src/constants/OIDCRequestConstants.ts @@ -78,7 +78,7 @@ const OIDCRequestConstants: { /** * The default scopes used in OIDC sign-in requests. */ - DEFAULT_SCOPES: [ScopeConstants.OPENID, ScopeConstants.PROFILE, ScopeConstants.INTERNAL_LOGIN], + DEFAULT_SCOPES: [ScopeConstants.OPENID, ScopeConstants.PROFILE], }, }, diff --git a/packages/javascript/src/constants/ScopeConstants.ts b/packages/javascript/src/constants/ScopeConstants.ts index 88ce28a..523e056 100644 --- a/packages/javascript/src/constants/ScopeConstants.ts +++ b/packages/javascript/src/constants/ScopeConstants.ts @@ -36,17 +36,9 @@ * ``` */ const ScopeConstants: { - INTERNAL_LOGIN: string; OPENID: string; PROFILE: string; } = { - /** - * The scope for accessing the user's profile information from SCIM. - * This scope allows the client to retrieve basic user information such as - * name, email, profile picture, etc. - */ - INTERNAL_LOGIN: 'internal_login', - /** * The base OpenID Connect scope. * Required for all OpenID Connect flows. Indicates that the client From 19cfa66a11701a0e111a667c5cbf66eb6b551d96 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 6 Jul 2026 23:15:42 +0530 Subject: [PATCH 2/3] Update quickstart samples --- packages/nextjs/src/ThunderIDNextClient.ts | 21 + .../components/presentation/SignIn/SignIn.tsx | 96 +- .../components/presentation/SignUp/SignUp.tsx | 13 +- .../presentation/UserAvatar/UserAvatar.tsx | 43 + .../contexts/ThunderID/ThunderIDProvider.tsx | 95 +- packages/nextjs/src/client/index.ts | 3 + .../nextjs/src/server/ThunderIDProvider.tsx | 24 +- .../nextjs/src/server/actions/signInAction.ts | 84 +- .../src/runtime/composables/useThunderID.ts | 23 +- .../presentation/auth/SignIn/BaseSignIn.tsx | 52 +- .../presentation/auth/SignUp/BaseSignUp.tsx | 10 +- .../contexts/FlowMeta/FlowMetaProvider.tsx | 26 +- packages/react/src/index.ts | 2 + .../src/components/auth/sign-up/BaseSignUp.ts | 10 +- pnpm-lock.yaml | 1747 ++++++++++++++++- samples/browser/quickstart/README.md | 21 +- samples/browser/quickstart/src/auth.js | 18 +- .../browser/quickstart/src/components/nav.js | 80 +- .../src/components/profileDialog.js | 112 ++ samples/browser/quickstart/src/main.js | 30 +- samples/browser/quickstart/src/pages/home.js | 49 +- .../browser/quickstart/src/pages/profile.js | 45 - samples/browser/quickstart/src/style.css | 459 +++-- .../thunderid-config/thunderid-config.yaml | 128 ++ .../quickstart/thunderid-config/thunderid.env | 2 + samples/express/quickstart/README.md | 54 +- samples/express/quickstart/index.js | 69 - samples/express/quickstart/index.mjs | 329 ++++ samples/express/quickstart/lib/auth.mjs | 47 + .../express/quickstart/lib/expressLogo.mjs | 10 + samples/express/quickstart/lib/layout.mjs | 96 + .../express/quickstart/lib/thunderMark.mjs | 8 + samples/express/quickstart/package.json | 5 +- ...Express-Quickstart.postman_collection.json | 47 + samples/express/quickstart/public/styles.css | 894 +++++++++ .../thunderid-config/thunderid-config.yaml | 122 ++ .../quickstart/thunderid-config/thunderid.env | 3 + samples/nextjs/quickstart/.env.example | 2 + samples/nextjs/quickstart/README.md | 36 +- .../app/components/ConfigNotice.tsx | 83 + .../quickstart/app/components/HeroCtas.tsx | 11 +- .../nextjs/quickstart/app/components/Nav.tsx | 33 +- .../app/components/icons/NextLogo.tsx | 17 + samples/nextjs/quickstart/app/globals.css | 160 +- samples/nextjs/quickstart/app/layout.tsx | 20 +- samples/nextjs/quickstart/app/page.tsx | 20 +- .../nextjs/quickstart/app/profile/page.tsx | 14 - samples/nextjs/quickstart/app/signin/page.tsx | 30 + samples/nextjs/quickstart/app/signup/page.tsx | 27 + .../thunderid-config/thunderid-config.yaml | 125 ++ .../quickstart/thunderid-config/thunderid.env | 4 + samples/node/quickstart/README.md | 26 +- samples/node/quickstart/index.js | 41 +- .../thunderid-config/thunderid-config.yaml | 122 ++ .../quickstart/thunderid-config/thunderid.env | 3 + samples/nuxt/quickstart/.env.example | 3 + samples/nuxt/quickstart/.env.local | 3 - samples/nuxt/quickstart/README.md | 36 +- samples/nuxt/quickstart/app.vue | 5 - samples/nuxt/quickstart/app/app.vue | 15 + .../quickstart/{ => app}/assets/styles.css | 110 ++ .../{ => app}/components/AppNav.vue | 47 +- .../app/components/ConfigNotice.vue | 78 + samples/nuxt/quickstart/app/pages/index.vue | 234 +++ samples/nuxt/quickstart/app/pages/signin.vue | 24 + samples/nuxt/quickstart/app/pages/signup.vue | 24 + .../nuxt/quickstart/{ => app}/pages/token.vue | 0 samples/nuxt/quickstart/nuxt.config.ts | 3 + samples/nuxt/quickstart/package.json | 4 +- samples/nuxt/quickstart/pages/index.vue | 243 --- samples/nuxt/quickstart/pages/profile.vue | 14 - .../thunderid-config/thunderid-config.yaml | 125 ++ .../quickstart/thunderid-config/thunderid.env | 4 + samples/react/quickstart/README.md | 21 +- samples/react/quickstart/src/App.css | 52 +- samples/react/quickstart/src/App.jsx | 2 - .../src/components/ConfigNotice.jsx | 85 + .../quickstart/src/components/HeroCtas.jsx | 11 +- .../react/quickstart/src/components/Nav.jsx | 35 +- .../src/components/icons/ReactLogo.jsx | 16 + samples/react/quickstart/src/main.jsx | 20 +- .../react/quickstart/src/pages/HomePage.jsx | 4 +- .../quickstart/src/pages/ProfilePage.jsx | 9 - .../thunderid-config/thunderid-config.yaml | 128 ++ .../quickstart/thunderid-config/thunderid.env | 2 + samples/vue/quickstart/README.md | 21 +- samples/vue/quickstart/src/App.vue | 9 +- .../src/components/ConfigNotice.vue | 80 + samples/vue/quickstart/src/components/Nav.vue | 25 +- samples/vue/quickstart/src/pages/HomePage.vue | 15 +- .../vue/quickstart/src/pages/ProfilePage.vue | 9 - samples/vue/quickstart/src/style.css | 53 +- .../thunderid-config/thunderid-config.yaml | 128 ++ .../quickstart/thunderid-config/thunderid.env | 2 + 94 files changed, 6199 insertions(+), 1151 deletions(-) create mode 100644 packages/nextjs/src/client/components/presentation/UserAvatar/UserAvatar.tsx create mode 100644 samples/browser/quickstart/src/components/profileDialog.js delete mode 100644 samples/browser/quickstart/src/pages/profile.js create mode 100644 samples/browser/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/browser/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/express/quickstart/index.js create mode 100644 samples/express/quickstart/index.mjs create mode 100644 samples/express/quickstart/lib/auth.mjs create mode 100644 samples/express/quickstart/lib/expressLogo.mjs create mode 100644 samples/express/quickstart/lib/layout.mjs create mode 100644 samples/express/quickstart/lib/thunderMark.mjs create mode 100644 samples/express/quickstart/postman/ThunderID-Express-Quickstart.postman_collection.json create mode 100644 samples/express/quickstart/public/styles.css create mode 100644 samples/express/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/express/quickstart/thunderid-config/thunderid.env create mode 100644 samples/nextjs/quickstart/app/components/ConfigNotice.tsx create mode 100644 samples/nextjs/quickstart/app/components/icons/NextLogo.tsx delete mode 100644 samples/nextjs/quickstart/app/profile/page.tsx create mode 100644 samples/nextjs/quickstart/app/signin/page.tsx create mode 100644 samples/nextjs/quickstart/app/signup/page.tsx create mode 100644 samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/nextjs/quickstart/thunderid-config/thunderid.env create mode 100644 samples/node/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/node/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/nuxt/quickstart/.env.local delete mode 100644 samples/nuxt/quickstart/app.vue create mode 100644 samples/nuxt/quickstart/app/app.vue rename samples/nuxt/quickstart/{ => app}/assets/styles.css (86%) rename samples/nuxt/quickstart/{ => app}/components/AppNav.vue (50%) create mode 100644 samples/nuxt/quickstart/app/components/ConfigNotice.vue create mode 100644 samples/nuxt/quickstart/app/pages/index.vue create mode 100644 samples/nuxt/quickstart/app/pages/signin.vue create mode 100644 samples/nuxt/quickstart/app/pages/signup.vue rename samples/nuxt/quickstart/{ => app}/pages/token.vue (100%) delete mode 100644 samples/nuxt/quickstart/pages/index.vue delete mode 100644 samples/nuxt/quickstart/pages/profile.vue create mode 100644 samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/nuxt/quickstart/thunderid-config/thunderid.env create mode 100644 samples/react/quickstart/src/components/ConfigNotice.jsx create mode 100644 samples/react/quickstart/src/components/icons/ReactLogo.jsx delete mode 100644 samples/react/quickstart/src/pages/ProfilePage.jsx create mode 100644 samples/react/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/react/quickstart/thunderid-config/thunderid.env create mode 100644 samples/vue/quickstart/src/components/ConfigNotice.vue delete mode 100644 samples/vue/quickstart/src/pages/ProfilePage.vue create mode 100644 samples/vue/quickstart/thunderid-config/thunderid-config.yaml create mode 100644 samples/vue/quickstart/thunderid-config/thunderid.env diff --git a/packages/nextjs/src/ThunderIDNextClient.ts b/packages/nextjs/src/ThunderIDNextClient.ts index d27b18a..0963fae 100644 --- a/packages/nextjs/src/ThunderIDNextClient.ts +++ b/packages/nextjs/src/ThunderIDNextClient.ts @@ -20,6 +20,7 @@ import { ThunderIDNodeClient, ThunderIDRuntimeError, AuthClientConfig, + EmbeddedSignInFlowResponse, ExtendedAuthorizeRequestUrlParams, FlattenedSchema, IdToken, @@ -31,6 +32,7 @@ import { TokenResponse, User, UserProfile, + executeEmbeddedSignInFlow, extractUserClaimsFromIdToken, flattenUserSchema, generateFlattenedUserProfile, @@ -249,6 +251,25 @@ class ThunderIDNextClient e const arg3: any = args[2]; const arg4: any = args[3]; + // An embedded sign-in flow payload initiates or continues a `POST /flow/execute` step + // (identified by `applicationId` for a new flow or `executionId` to continue one). This is + // distinct from the OAuth authorization_code exchange handled by `super.signIn` below, which + // is used once the embedded flow completes and returns an authorization code. + const isEmbeddedFlowPayload: boolean = + typeof arg1 === 'object' && arg1 !== null && ('executionId' in arg1 || 'applicationId' in arg1); + + if (isEmbeddedFlowPayload) { + await this.ensureInitialized(); + + const configData: AuthClientConfig = await this.getStorageManager().getConfigData(); + + return executeEmbeddedSignInFlow({ + baseUrl: configData?.baseUrl, + payload: arg1, + url: arg2?.url, + }) as unknown as Promise; + } + return super.signIn(arg4, arg3, arg1?.code, arg1?.session_state, arg1?.state, arg1) as unknown as Promise; } diff --git a/packages/nextjs/src/client/components/presentation/SignIn/SignIn.tsx b/packages/nextjs/src/client/components/presentation/SignIn/SignIn.tsx index f0f7d8c..5b79908 100644 --- a/packages/nextjs/src/client/components/presentation/SignIn/SignIn.tsx +++ b/packages/nextjs/src/client/components/presentation/SignIn/SignIn.tsx @@ -18,9 +18,9 @@ 'use client'; -import {ThunderIDRuntimeError} from '@thunderid/node'; -import {BaseSignIn, BaseSignInProps} from '@thunderid/react'; -import {FC} from 'react'; +import {EmbeddedFlowComponent, EmbeddedFlowType, ThunderIDRuntimeError} from '@thunderid/node'; +import {BaseSignIn, BaseSignInProps, normalizeFlowResponse, useTranslation} from '@thunderid/react'; +import {FC, useEffect, useRef, useState} from 'react'; import useThunderID from '../../../contexts/ThunderID/useThunderID'; /** @@ -31,13 +31,74 @@ export type SignInProps = Pick = ({size = 'medium', variant = 'outlined', ...rest}: SignInProps) => { - const {signIn} = useThunderID(); +const SignIn: FC = ({size = 'medium', variant = 'outlined', onError, ...rest}: SignInProps) => { + const {signIn, applicationId, scopes} = useThunderID(); + const {t} = useTranslation(); - const handleOnSubmit = async (payload: any, request: any): Promise => { + const [components, setComponents] = useState([]); + const [additionalData, setAdditionalData] = useState>({}); + const [executionId, setExecutionId] = useState(undefined); + const [isFlowInitialized, setIsFlowInitialized] = useState(false); + const [flowError, setFlowError] = useState(null); + const initializationAttemptedRef = useRef(false); + // A fresh challenge token is required on every step to prevent replay attacks; the server + // rotates it on each response, so it's tracked in a ref (not state) to avoid a stale value + // from a closure captured before the previous response's state update committed. + const challengeTokenRef = useRef(undefined); + + const applyFlowResponse = (response: any): void => { + const normalized = normalizeFlowResponse(response, t, {resolveTranslations: false}); + + challengeTokenRef.current = response?.challengeToken ?? undefined; + setExecutionId(normalized.executionId ?? undefined); + setComponents(normalized.components); + setAdditionalData(normalized.additionalData); + }; + + const handleFlowError = (error: unknown): void => { + const normalizedError: Error = error instanceof Error ? error : new Error(String(error)); + + setFlowError(normalizedError); + onError?.(normalizedError); + }; + + useEffect(() => { + if (initializationAttemptedRef.current || !signIn) { + return; + } + + initializationAttemptedRef.current = true; + + (async (): Promise => { + try { + const response: any = await signIn( + { + applicationId, + flowType: EmbeddedFlowType.Authentication, + ...(scopes && {scopes}), + }, + {}, + ); + + // `response` is undefined when the provider already redirected + // (OAuth-redirect or completed flows are handled there directly). + if (response) { + applyFlowResponse(response); + } + } catch (error) { + handleFlowError(error); + } finally { + setIsFlowInitialized(true); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [signIn]); + + const handleOnSubmit = async (payload: any): Promise => { if (!signIn) { throw new ThunderIDRuntimeError( '`signIn` function is not available.', @@ -46,12 +107,27 @@ const SignIn: FC = ({size = 'medium', variant = 'outlined', ...rest ); } - await signIn(payload, request); + const response: any = await signIn( + { + ...payload, + executionId, + ...(challengeTokenRef.current ? {challengeToken: challengeTokenRef.current} : {}), + }, + {}, + ); + + if (response) { + applyFlowResponse(response); + } }; return ( = ({ ); } - return (await signUp( - payload || { - flowType: EmbeddedFlowType.Registration, - ...(contextApplicationId && {applicationId: contextApplicationId}), - ...(scopes && {scopes}), - }, - )) as unknown as Promise; + return (await signUp({ + flowType: EmbeddedFlowType.Registration, + ...(contextApplicationId && {applicationId: contextApplicationId}), + ...(scopes && {scopes}), + ...payload, + })) as unknown as Promise; }; /** diff --git a/packages/nextjs/src/client/components/presentation/UserAvatar/UserAvatar.tsx b/packages/nextjs/src/client/components/presentation/UserAvatar/UserAvatar.tsx new file mode 100644 index 0000000..37df25f --- /dev/null +++ b/packages/nextjs/src/client/components/presentation/UserAvatar/UserAvatar.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +'use client'; + +import {BaseUserAvatar, BaseUserAvatarProps} from '@thunderid/react'; +import {FC, ReactElement} from 'react'; +import useThunderID from '../../../contexts/ThunderID/useThunderID'; + +export type UserAvatarProps = Omit; + +/** + * UserAvatar component renders an avatar for the currently signed-in user. + * This component is the Next.js-specific implementation that uses BaseUserAvatar + * and automatically retrieves the user data from ThunderID context. + * + * @example + * ```tsx + * + * ``` + */ +const UserAvatar: FC = (props: UserAvatarProps): ReactElement => { + const {user} = useThunderID(); + + return ; +}; + +export default UserAvatar; diff --git a/packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx b/packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx index 498faab..c18f6a7 100644 --- a/packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx +++ b/packages/nextjs/src/client/contexts/ThunderID/ThunderIDProvider.tsx @@ -20,6 +20,7 @@ import { EmbeddedFlowExecuteRequestConfig, + FlowMetadataResponse, generateFlattenedUserProfile, UpdateMeProfileConfig, User, @@ -32,6 +33,8 @@ import { FlowProvider, UserProvider, ThemeProvider, + ThunderIDContext as ReactThunderIDContext, + ThunderIDContextProps as ReactThunderIDContextProps, ThunderIDProviderProps, getActiveTheme, } from '@thunderid/react'; @@ -55,6 +58,11 @@ export type ThunderIDClientProviderProps = Partial Promise<{error?: string; redirectUrl?: string; success: boolean}>; + /** + * Flow metadata fetched server-side ahead of time, seeding `FlowMetaProvider` so it can skip + * its own initial client-side fetch (avoiding a flash of untranslated i18n keys). + */ + initialMeta?: FlowMetadataResponse | null; isSignedIn: boolean; organizationHandle: ThunderIDContextProps['organizationHandle']; refreshToken: () => Promise; @@ -88,6 +96,7 @@ const ThunderIDClientProvider: FC) => { const reRenderCheckRef: RefObject = useRef(false); const router: AppRouterInstance = useRouter(); @@ -298,19 +307,83 @@ const ThunderIDClientProvider: FC Promise) => { + return () => Promise.reject(new ThunderIDRuntimeError(`\`${name}\` is not supported in @thunderid/nextjs.`, `ThunderIDClientProvider-${name}-NotSupportedError-001`, 'nextjs')); + }; + + const reactContextValue: ReactThunderIDContextProps = useMemo( + () => ({ + afterSignInUrl: undefined, + applicationId, + baseUrl, + clearSession, + clientId: undefined, + discovery: {wellKnown: null}, + exchangeToken: unsupported('exchangeToken'), + getAccessToken: unsupported('getAccessToken'), + getDecodedIdToken: unsupported('getDecodedIdToken'), + getIdToken: unsupported('getIdToken'), + getStorageManager: () => Promise.resolve(null), + http: { + request: unsupported('http.request'), + requestAll: unsupported('http.requestAll'), + }, + instanceId: 0, + isInitialized: !isLoading, + isLoading, + isMetaLoading: false, + isSignedIn, + meta: initialMeta ?? null, + organizationHandle, + reInitialize: unsupported('reInitialize'), + recover: unsupported('recover'), + resolveFlowTemplateLiterals: (text: string | undefined): string => text ?? '', + scopes, + signIn: handleSignIn, + signInSilently: unsupported('signInSilently'), + signInUrl, + signOut: handleSignOut, + signUp: handleSignUp, + signUpUrl, + user, + }), + [ + applicationId, + baseUrl, + clearSession, + isLoading, + isSignedIn, + organizationHandle, + scopes, + signInUrl, + signUpUrl, + user, + initialMeta, + ], + ); + return ( - - - - - - {children} - - - - - + + + + + + + {children} + + + + + + ); }; diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 39c3a01..73a259c 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -45,5 +45,8 @@ export type {SignUpProps} from './components/presentation/SignUp/SignUp'; export {default as UserDropdown} from './components/presentation/UserDropdown/UserDropdown'; export type {UserDropdownProps} from './components/presentation/UserDropdown/UserDropdown'; +export {default as UserAvatar} from './components/presentation/UserAvatar/UserAvatar'; +export type {UserAvatarProps} from './components/presentation/UserAvatar/UserAvatar'; + export {default as UserProfile} from './components/presentation/UserProfile/UserProfile'; export type {UserProfileProps} from './components/presentation/UserProfile/UserProfile'; diff --git a/packages/nextjs/src/server/ThunderIDProvider.tsx b/packages/nextjs/src/server/ThunderIDProvider.tsx index e10c6eb..4878988 100644 --- a/packages/nextjs/src/server/ThunderIDProvider.tsx +++ b/packages/nextjs/src/server/ThunderIDProvider.tsx @@ -18,7 +18,15 @@ 'use server'; -import {ThunderIDRuntimeError, IdToken, User, UserProfile} from '@thunderid/node'; +import { + ThunderIDRuntimeError, + FlowMetadataResponse, + FlowMetaType, + IdToken, + User, + UserProfile, + getFlowMeta, +} from '@thunderid/node'; import {ThunderIDProviderProps} from '@thunderid/react'; import {FC, PropsWithChildren, ReactElement} from 'react'; import clearSession from './actions/clearSession'; @@ -106,6 +114,19 @@ const ThunderIDServerProvider: FC; } + // Fetch flow metadata (design config + i18n bundle) server-side so `FlowMetaProvider` can seed + // its state and skip a redundant client-side fetch — avoiding a flash of untranslated i18n keys + // (e.g. raw `signin.forms.credentials.title`) while embedded flow components first render. + let flowMeta: FlowMetadataResponse | null = null; + try { + flowMeta = await getFlowMeta({ + baseUrl: config?.baseUrl, + ...(config?.applicationId ? {id: config.applicationId, type: FlowMetaType.App} : {}), + }); + } catch (error) { + logger.warn('[ThunderIDServerProvider] Failed to fetch flow metadata:', error?.toString()); + } + // Try to get session information from JWT first, then fall back to legacy const sessionPayload: SessionTokenPayload | undefined = await getSessionPayload(); const sessionId: string = sessionPayload?.sessionId || (await getSessionId()) || ''; @@ -164,6 +185,7 @@ const ThunderIDServerProvider: FC = await client.signIn( - { - code: response?.authData?.code, - session_state: response?.authData?.session_state, - state: response?.authData?.state, - } as any, - {}, + // On the V2 platform, a completed embedded flow (without a preceding `/authorize` redirect, + // i.e. no `authId`) returns a self-contained JWT `assertion` rather than an authorization + // code to exchange. This mirrors how @thunderid/react and @thunderid/vue treat it: the + // assertion is used directly as the bearer session token. + if (!response.assertion) { + throw new Error('[signInAction] Flow completed without an assertion.'); + } + + const idToken: IdToken = await client.getDecodedIdToken(sessionId, response.assertion); + const userIdFromToken: string = (idToken.sub as string | undefined) ?? sessionId; + const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as + | string + | undefined; + const scopes: string = (idToken['scope'] as string | undefined) ?? ''; + const iat: number = (idToken['iat'] as number | undefined) ?? Math.floor(Date.now() / 1000); + const exp: number = (idToken['exp'] as number | undefined) ?? iat + 3600; + const expiresIn: number = Math.max(exp - iat, 0); + + const config: ThunderIDNextConfig = await client.getConfiguration(); + const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry( + config.sessionCookie?.expiryTime, + ); + + const sessionToken: string = await SessionManager.createSessionToken( + response.assertion, + userIdFromToken, sessionId, + scopes, + expiresIn, + '', + organizationId, ); - if (signInResult) { - const idToken: IdToken = await client.getDecodedIdToken( - sessionId, - (signInResult['idToken'] || signInResult['id_token']) as string, - ); - const userIdFromToken: string = (idToken.sub || signInResult['sub'] || sessionId) as string; - const {accessToken}: {accessToken: string} = signInResult as {accessToken: string}; - const refreshToken: string = (signInResult['refreshToken'] as string | undefined) ?? ''; - const scopes: string = signInResult['scope'] as string; - const organizationId: string | undefined = (idToken['user_org'] || idToken['organization_id']) as - | string - | undefined; - const rawExpiresIn: unknown = signInResult['expiresIn'] ?? signInResult['expires_in']; - const expiresIn = Number(rawExpiresIn); - if (Number.isNaN(expiresIn)) { - throw new Error(`[signInAction] Invalid expiresIn value received: ${rawExpiresIn}`); - } - const config: ThunderIDNextConfig = await client.getConfiguration(); - const sessionCookieExpiryTime: number = SessionManager.resolveSessionCookieExpiry( - config.sessionCookie?.expiryTime, - ); - - const sessionToken: string = await SessionManager.createSessionToken( - accessToken, - userIdFromToken, - sessionId, - scopes, - expiresIn, - refreshToken, - organizationId, - ); - - cookieStore.set( - SessionManager.getSessionCookieName(), - sessionToken, - SessionManager.getSessionCookieOptions(sessionCookieExpiryTime), - ); - - cookieStore.delete(SessionManager.getTempSessionCookieName()); - } + cookieStore.set( + SessionManager.getSessionCookieName(), + sessionToken, + SessionManager.getSessionCookieOptions(sessionCookieExpiryTime), + ); + + cookieStore.delete(SessionManager.getTempSessionCookieName()); const afterSignInUrl: string = await (await client.getStorageManager()).getConfigDataParameter('afterSignInUrl'); return {data: {afterSignInUrl: String(afterSignInUrl)}, success: true}; diff --git a/packages/nuxt/src/runtime/composables/useThunderID.ts b/packages/nuxt/src/runtime/composables/useThunderID.ts index b806e6f..40d4bec 100644 --- a/packages/nuxt/src/runtime/composables/useThunderID.ts +++ b/packages/nuxt/src/runtime/composables/useThunderID.ts @@ -17,7 +17,7 @@ */ import {navigateTo, useState, useRuntimeConfig} from '#app'; -import {EmbeddedSignInFlowStatus, getRedirectBasedSignUpUrl} from '@thunderid/browser'; +import {EmbeddedSignInFlowStatus, EmbeddedSignUpFlowStatus, getRedirectBasedSignUpUrl} from '@thunderid/browser'; import {useThunderID as useThunderIDVue, type ThunderIDContext} from '@thunderid/vue'; import type {Ref} from 'vue'; import type {ThunderIDAuthState} from '../types'; @@ -127,6 +127,27 @@ export function useThunderID(): ThunderIDContext { const signUp = async (...args: any[]): Promise => { const payload: unknown = args[0]; + // Embedded-flow path: arg0 is a non-null object with a `flowType` key + // (see `ThunderIDSignUp`'s `handleInitialize`/`handleOnSubmit`). + const isEmbedded: boolean = typeof payload === 'object' && payload !== null && 'flowType' in payload; + + if (isEmbedded) { + const res: {data: any; success: boolean} = await $fetch<{data: any; success: boolean}>('/api/auth/signup', { + body: {payload}, + method: 'POST', + }); + + // Flow complete — the server route replies with `{ afterSignUpUrl }` + // (no `flowStatus`). Synthesize one so `BaseSignUp`'s completion check + // (`response.flowStatus === Complete`) fires and ``'s + // `handleComplete` drives the post-registration redirect. + if (res.data?.afterSignUpUrl) { + return {afterSignUpUrl: res.data.afterSignUpUrl, flowStatus: EmbeddedSignUpFlowStatus.Complete}; + } + + return res.data; + } + // Redirect flow. const cfg: { applicationId?: string; diff --git a/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx b/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx index 780238a..6d7ce50 100644 --- a/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx +++ b/packages/react/src/components/presentation/auth/SignIn/BaseSignIn.tsx @@ -24,9 +24,8 @@ import { FieldError, FlowMetadataResponse, Preferences, - buildValidatorFromRules, } from '@thunderid/browser'; -import {FC, useEffect, useState, useCallback, useContext, ReactElement, ReactNode} from 'react'; +import {FC, useState, useCallback, useContext, ReactElement, ReactNode} from 'react'; import useStyles from './BaseSignIn.styles'; import ComponentRendererContext, { ComponentRendererMap, @@ -61,12 +60,7 @@ export interface BaseSignInRenderProps { error?: Error | null; /** - * Field validation errors keyed by component ref. Populated from BOTH: - * - Client-side rule evaluation (component.validation rules in meta.components) - * - Server-side validation failures (data.fieldErrors in the flow response) - * When the server returns multiple failing rules for one field, only the first - * message is exposed here. The full FieldError[] array is available on the raw - * response object (and is reflected into the BaseSignIn `serverFieldErrors` prop). + * Field validation errors */ fieldErrors: Record; @@ -254,7 +248,6 @@ const BaseSignInContent: FC = ({ children, additionalData = {}, isTimeoutDisabled = false, - serverFieldErrors = null, }: BaseSignInProps): ReactElement => { const {meta} = useThunderID(); const {theme} = useTheme(); @@ -303,12 +296,9 @@ const BaseSignInContent: FC = ({ component.type === 'PASSWORD_INPUT' || component.type === 'EMAIL_INPUT' || component.type === 'PHONE_INPUT' || - component.type === 'OTP_INPUT' || - component.type === 'SELECT' || - component.type === 'DATE_INPUT' + component.type === 'OTP_INPUT' ) { const identifier: string = component.ref; - const ruleValidator = buildValidatorFromRules(component.validation); fields.push({ initialValue: '', name: identifier, @@ -325,15 +315,6 @@ const BaseSignInContent: FC = ({ ) { return t('field.email.invalid'); } - // Evaluate declarative validation rules from meta.components[].validation. - // The composed validator returns the first failing rule's message (i18n key or - // literal string) so it can be passed straight to the i18n layer for display. - if (ruleValidator && value) { - const ruleMessage = ruleValidator(value); - if (ruleMessage) { - return t(ruleMessage); - } - } return null; }, @@ -368,37 +349,10 @@ const BaseSignInContent: FC = ({ isValid: isFormValid, setValue: setFormValue, setTouched: setFormTouched, - setErrors: setFormErrors, - clearErrors: clearFormErrors, validateForm, touchAllFields, } = form; - /** - * Project server-side validation errors (from `data.fieldErrors`) into the form's - * `errors` state so they surface through the same render-prop / UI as client-side - * errors. When the server returns multiple failing rules for one field, only the - * first message is shown — matching the SDK's single-string-per-field contract. - * The full FieldError[] remains available via the `serverFieldErrors` prop. - * - * Also marks each affected field as `touched` so the error renders immediately — - * `useForm` only shows errors for touched fields by default. - */ - useEffect(() => { - clearFormErrors(); - if (!serverFieldErrors || serverFieldErrors.length === 0) { - return; - } - const errors: Record = {}; - for (const fe of serverFieldErrors) { - if (!(fe.identifier in errors)) { - errors[fe.identifier] = fe.message; - } - } - setFormErrors(errors); - Object.keys(errors).forEach((field: string) => setFormTouched(field, true)); - }, [serverFieldErrors, setFormErrors, setFormTouched, clearFormErrors]); - /** * Handle input value changes. * Only updates the value without marking as touched. diff --git a/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx b/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx index 96eb248..85b0b01 100644 --- a/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx +++ b/packages/react/src/components/presentation/auth/SignUp/BaseSignUp.tsx @@ -29,7 +29,7 @@ import { buildValidatorFromRules, Preferences, } from '@thunderid/browser'; -import {FC, ReactElement, ReactNode, useCallback, useContext, useEffect, useRef, useState} from 'react'; +import {FC, ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import useStyles from './BaseSignUp.styles'; import ComponentRendererContext, { ComponentRendererMap, @@ -455,9 +455,11 @@ const BaseSignUpContent: FC = ({ [t], ); - const formFields: any = (currentFlow?.data as any)?.components - ? extractFormFields((currentFlow!.data as any).components) - : []; + const formFields: any = useMemo( + () => + (currentFlow?.data as any)?.components ? extractFormFields((currentFlow!.data as any).components) : [], + [currentFlow, extractFormFields], + ); const form: any = useForm>({ fields: formFields, diff --git a/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx b/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx index 1964821..cf26829 100644 --- a/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx +++ b/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx @@ -35,6 +35,14 @@ export interface FlowMetaProviderProps { * @default true */ enabled?: boolean; + + /** + * Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed + * the provider's state. When present, the provider skips its own initial client-side fetch — + * avoiding a redundant request and the flash of untranslated i18n keys while that fetch is in + * flight — but still fetches normally on subsequent changes (e.g. an explicit language switch). + */ + initialMeta?: FlowMetadataResponse | null; } /** @@ -63,12 +71,13 @@ export interface FlowMetaProviderProps { const FlowMetaProvider: FC> = ({ children, enabled = true, + initialMeta = null, }: PropsWithChildren): ReactElement => { const {baseUrl, applicationId, isInitialized} = useThunderID(); const i18nContext: I18nContextValue = useI18n(); - const [meta, setMeta] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [meta, setMeta] = useState(initialMeta); + const [isLoading, setIsLoading] = useState(!initialMeta); const [error, setError] = useState(null); const [pendingLanguage, setPendingLanguage] = useState(null); @@ -80,6 +89,7 @@ const FlowMetaProvider: FC> = ({ // 2. Rapid dependency changes (e.g. baseUrl stabilising) that produce two // effect firings before the first fetch completes. const lastFetchedRef: RefObject<(() => Promise) | null> = useRef<(() => Promise) | null>(null); + const initialMetaConsumedRef: RefObject = useRef(false); const fetchFlowMeta: () => Promise = useCallback(async (): Promise => { if (!enabled) { setMeta(null); @@ -161,6 +171,17 @@ const FlowMetaProvider: FC> = ({ }, [pendingLanguage, i18nContext?.setLanguage]); useEffect(() => { + if (!initialMetaConsumedRef.current) { + initialMetaConsumedRef.current = true; + + if (initialMeta) { + // Seeded from SSR (or another caller) — skip the redundant first client-side fetch. + // Later dependency changes (e.g. an explicit language switch) still fetch normally. + lastFetchedRef.current = fetchFlowMeta; + return; + } + } + if (lastFetchedRef.current === fetchFlowMeta) { // Same reference as the last dispatch — this is a StrictMode re-mount // or an effect re-fire with unchanged deps. Skip to avoid a duplicate fetch. @@ -169,6 +190,7 @@ const FlowMetaProvider: FC> = ({ lastFetchedRef.current = fetchFlowMeta; fetchFlowMeta(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [fetchFlowMeta]); // When meta loads with i18n translations, inject them into the i18n system. diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 398ed42..8d23548 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -65,6 +65,8 @@ export * from './hooks/useBrowserUrl'; export {default as useTranslation} from './hooks/useTranslation'; export * from './hooks/useTranslation'; +export {normalizeFlowResponse, extractErrorMessage} from './utils/flowTransformer'; + export {default as useForm} from './hooks/useForm'; export {default as BaseSignInButton} from './components/actions/SignInButton/BaseSignInButton'; diff --git a/packages/vue/src/components/auth/sign-up/BaseSignUp.ts b/packages/vue/src/components/auth/sign-up/BaseSignUp.ts index a7feacb..008e9dc 100644 --- a/packages/vue/src/components/auth/sign-up/BaseSignUp.ts +++ b/packages/vue/src/components/auth/sign-up/BaseSignUp.ts @@ -529,9 +529,13 @@ const BaseSignUp: Component = defineComponent({ watch( () => [props.isInitialized, isFlowInitialized.value] as [boolean, boolean], ([initialized, flowInit]: [boolean, boolean]) => { - // Skip if URL has OAuth code params - const urlParams: URLSearchParams = new URL(window.location.href).searchParams; - if (urlParams.get('code') || urlParams.get('state')) return; + // Skip if URL has OAuth code params. `window` is unavailable during SSR + // (e.g. Nuxt) — initialization simply proceeds and the check re-runs + // once this watch re-evaluates on the client. + if (typeof window !== 'undefined') { + const urlParams: URLSearchParams = new URL(window.location.href).searchParams; + if (urlParams.get('code') || urlParams.get('state')) return; + } if (initialized && !flowInit && !initializationAttempted) { initializationAttempted = true; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebd0696..caebd75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -341,7 +341,7 @@ importers: dependencies: '@thunderid/javascript': specifier: workspace:^ - version: link:../javascript + version: link:../javascript/dist base64url: specifier: 'catalog:' version: 3.0.1 @@ -407,11 +407,81 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/browser/dist: + dependencies: + '@thunderid/javascript': + specifier: workspace:^ + version: link:../../javascript/dist + base64url: + specifier: 3.0.1 + version: 3.0.1 + buffer: + specifier: 6.0.3 + version: 6.0.3 + core-js: + specifier: 3.42.0 + version: 3.42.0 + fast-sha256: + specifier: 1.3.0 + version: 1.3.0 + jose: + specifier: 5.2.0 + version: 5.2.0 + process: + specifier: 0.11.10 + version: 0.11.10 + randombytes: + specifier: 2.1.0 + version: 2.1.0 + stream-browserify: + specifier: 3.0.0 + version: 3.0.0 + tslib: + specifier: 2.8.1 + version: 2.8.1 + devDependencies: + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + '@vitest/browser-playwright': + specifier: 4.1.8 + version: 4.1.8(playwright@1.60.0)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@4.1.8) + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + jsdom: + specifier: 27.0.1 + version: 27.0.1 + playwright: + specifier: 1.60.0 + version: 1.60.0 + prettier: + specifier: 3.6.2 + version: 3.6.2 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/express: dependencies: '@thunderid/node': specifier: workspace:^ - version: link:../node + version: link:../node/dist uuid: specifier: 'catalog:' version: 11.1.1 @@ -453,6 +523,52 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@22.15.3)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@22.15.3)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/express/dist: + dependencies: + '@thunderid/node': + specifier: workspace:^ + version: link:../../node/dist + uuid: + specifier: 11.1.1 + version: 11.1.1 + devDependencies: + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@types/cookie-parser': + specifier: ^1.4.8 + version: 1.4.10(@types/express@5.0.6) + '@types/express': + specifier: ^5.0.6 + version: 5.0.6 + '@types/node': + specifier: 22.15.3 + version: 22.15.3 + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + express: + specifier: 5.2.1 + version: 5.2.1 + prettier: + specifier: 3.6.2 + version: 3.6.2 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@22.15.3)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@22.15.3)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/javascript: dependencies: jose: @@ -490,11 +606,48 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/javascript/dist: + dependencies: + jose: + specifier: 5.2.0 + version: 5.2.0 + tslib: + specifier: 2.8.1 + version: 2.8.1 + devDependencies: + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/nextjs: dependencies: '@thunderid/node': specifier: workspace:^ - version: link:../node + version: link:../node/dist '@thunderid/react': specifier: workspace:^ version: link:../react @@ -546,7 +699,7 @@ importers: dependencies: '@thunderid/javascript': specifier: workspace:^ - version: link:../javascript + version: link:../javascript/dist base64url: specifier: 'catalog:' version: 3.0.1 @@ -600,6 +753,64 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/node/dist: + dependencies: + '@thunderid/javascript': + specifier: workspace:^ + version: link:../../javascript/dist + base64url: + specifier: 3.0.1 + version: 3.0.1 + cross-fetch: + specifier: 4.1.0 + version: 4.1.0 + fast-sha256: + specifier: 1.3.0 + version: 1.3.0 + jose: + specifier: 5.2.0 + version: 5.2.0 + memory-cache: + specifier: 0.2.0 + version: 0.2.0 + secure-random-bytes: + specifier: 5.0.1 + version: 5.0.1 + tslib: + specifier: 2.8.1 + version: 2.8.1 + uuid: + specifier: 11.1.1 + version: 11.1.1 + devDependencies: + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/nuxt: dependencies: '@nuxt/kit': @@ -607,13 +818,13 @@ importers: version: 3.21.7(magicast@0.5.3) '@thunderid/browser': specifier: workspace:^ - version: link:../browser + version: link:../browser/dist '@thunderid/node': specifier: workspace:^ - version: link:../node + version: link:../node/dist '@thunderid/vue': specifier: workspace:^ - version: link:../vue + version: link:../vue/dist defu: specifier: 6.1.5 version: 6.1.5 @@ -671,7 +882,7 @@ importers: version: 0.27.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@thunderid/browser': specifier: workspace:^ - version: link:../browser + version: link:../browser/dist dompurify: specifier: 'catalog:' version: 3.4.11 @@ -777,6 +988,61 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/react-router/dist: + dependencies: + tslib: + specifier: 2.8.1 + version: 2.8.1 + devDependencies: + '@playwright/test': + specifier: 1.60.0 + version: 1.60.0 + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@thunderid/react': + specifier: workspace:^ + version: link:../../react + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + '@types/react': + specifier: 19.2.14 + version: 19.2.14 + '@vitest/browser-playwright': + specifier: 4.1.8 + version: 4.1.8(playwright@1.60.0)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vitest@4.1.8) + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + react-router: + specifier: 7.15.1 + version: 7.15.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/tanstack-router: dependencies: tslib: @@ -826,11 +1092,60 @@ importers: specifier: 'catalog:' version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/tanstack-router/dist: + dependencies: + tslib: + specifier: 2.8.1 + version: 2.8.1 + devDependencies: + '@tanstack/react-router': + specifier: 1.154.7 + version: 1.154.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@testing-library/react': + specifier: 16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@thunderid/react': + specifier: workspace:^ + version: link:../../react + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + '@types/react': + specifier: 19.2.14 + version: 19.2.14 + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 + react: + specifier: 19.2.3 + version: 19.2.3 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + packages/vue: dependencies: '@thunderid/browser': specifier: workspace:^ - version: link:../browser + version: link:../browser/dist dompurify: specifier: 'catalog:' version: 3.4.11 @@ -875,11 +1190,60 @@ importers: specifier: 3.5.30 version: 3.5.30(typescript@5.9.3) + packages/vue/dist: + dependencies: + '@thunderid/browser': + specifier: workspace:^ + version: link:../../browser/dist + dompurify: + specifier: 3.4.11 + version: 3.4.11 + tslib: + specifier: 2.8.1 + version: 2.8.1 + vue-router: + specifier: '>=4.0.0' + version: 5.1.0(@vue/compiler-sfc@3.5.38)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) + devDependencies: + '@thunderid/eslint-plugin': + specifier: 0.0.2 + version: 0.0.2(@typescript-eslint/utils@8.61.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)(vitest@4.1.8)(vue-eslint-parser@10.4.1(eslint@9.39.4(jiti@2.7.0))) + '@thunderid/prettier-config': + specifier: 0.0.2 + version: 0.0.2 + '@types/node': + specifier: 24.7.2 + version: 24.7.2 + '@vue/test-utils': + specifier: 2.4.6 + version: 2.4.6 + eslint: + specifier: 9.39.4 + version: 9.39.4(jiti@2.7.0) + prettier: + specifier: 3.6.2 + version: 3.6.2 + rimraf: + specifier: 6.1.3 + version: 6.1.3 + rolldown: + specifier: 1.0.0-beta.45 + version: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.1.8 + version: 4.1.8(@types/node@24.7.2)(@vitest/browser-playwright@4.1.8)(jsdom@27.0.1)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + vue: + specifier: 3.5.30 + version: 3.5.30(typescript@5.9.3) + samples/browser/quickstart: dependencies: '@thunderid/browser': specifier: workspace:* - version: link:../../../packages/browser + version: link:../../../packages/browser/dist devDependencies: vite: specifier: ^6.3.5 @@ -889,7 +1253,7 @@ importers: dependencies: '@thunderid/express': specifier: workspace:* - version: link:../../../packages/express + version: link:../../../packages/express/dist cookie-parser: specifier: ^1.4.7 version: 1.4.7 @@ -929,7 +1293,7 @@ importers: dependencies: '@thunderid/node': specifier: workspace:* - version: link:../../../packages/node + version: link:../../../packages/node/dist samples/nuxt/quickstart: dependencies: @@ -937,8 +1301,8 @@ importers: specifier: workspace:* version: link:../../../packages/nuxt nuxt: - specifier: ^3.16.2 - version: 3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) + specifier: ^4.4.8 + version: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) vue: specifier: ^3.5.13 version: 3.5.30(typescript@5.9.3) @@ -950,7 +1314,7 @@ importers: version: link:../../../packages/react '@thunderid/react-router': specifier: workspace:* - version: link:../../../packages/react-router + version: link:../../../packages/react-router/dist react: specifier: ^19.2.3 version: 19.2.3 @@ -972,7 +1336,7 @@ importers: dependencies: '@thunderid/vue': specifier: workspace:* - version: link:../../../packages/vue + version: link:../../../packages/vue/dist vue: specifier: ^3.5.13 version: 3.5.30(typescript@5.9.3) @@ -2154,10 +2518,30 @@ packages: peerDependencies: nuxt: ^3.21.7 + '@nuxt/nitro-server@4.4.8': + resolution: {integrity: sha512-cc1fxgSx34Htesx3JBO+hMhbqd6VljXDC06P+UOA5z53cR224TmEFYT/MUuZDkrtt4qLnSG8yq0IxhEM3NCUlw==} + engines: {node: ^22.12.0 || ^24.11.0 || >=26.0.0} + peerDependencies: + '@babel/plugin-proposal-decorators': ^7.25.0 + '@babel/plugin-syntax-typescript': ^7.25.0 + '@rollup/plugin-babel': ^6.0.0 || ^7.0.0 + nuxt: ^4.4.8 + peerDependenciesMeta: + '@babel/plugin-proposal-decorators': + optional: true + '@babel/plugin-syntax-typescript': + optional: true + '@rollup/plugin-babel': + optional: true + '@nuxt/schema@3.21.7': resolution: {integrity: sha512-H35IqlFu4YHXnW0Aw24yCpRuWOJjd9iwS6u9vinsopJo8rrM1/2aKMajX3l9ifhni1XN8nqcKo622z+5ZCQM4w==} engines: {node: ^14.18.0 || >=16.10.0} + '@nuxt/schema@4.4.8': + resolution: {integrity: sha512-igfWuMF0x0Pmx/XwhPwH/bcXgbuwNnjUjqxCAsY6VQhmGKo0e9soJq3Q0ohj+rBkBfX6o2ysTP1/t2M82aK4qA==} + engines: {node: ^14.18.0 || >=16.10.0} + '@nuxt/telemetry@2.8.0': resolution: {integrity: sha512-zAwXY24KYvpLTmiV+osagd2EHkfs5IF+7oDZYTQoit5r0kPlwaCNlzHp5I/wUAWT4LBw6lG8gZ6bWidAdv/erQ==} engines: {node: '>=18.12.0'} @@ -2215,6 +2599,26 @@ packages: rollup-plugin-visualizer: optional: true + '@nuxt/vite-builder@4.4.8': + resolution: {integrity: sha512-54M/k6qVY85Qeoe1m/lPZ0SANGJEbI50r5uYgh3XT942ENve3K5Nk6TMYp8i5wGGC4TWvPea+1mlCrp8rjsXag==} + engines: {node: ^22.12.0 || ^24.11.0 || >=26.0.0} + peerDependencies: + '@babel/plugin-proposal-decorators': ^7.25.0 + '@babel/plugin-syntax-jsx': ^7.25.0 + nuxt: 4.4.8 + rolldown: ^1.0.0-beta.38 + rollup-plugin-visualizer: ^6.0.0 || ^7.0.1 + vue: ^3.3.4 + peerDependenciesMeta: + '@babel/plugin-proposal-decorators': + optional: true + '@babel/plugin-syntax-jsx': + optional: true + rolldown: + optional: true + rollup-plugin-visualizer: + optional: true + '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -2224,38 +2628,80 @@ packages: cpu: [arm] os: [android] + '@oxc-minify/binding-android-arm-eabi@0.133.0': + resolution: {integrity: sha512-D8M1+nqwLaACHZsld/t6f+cE4N97XOu5iQ88f1ZaYH4ptFzFrXo5N7wUKACTI4xmNUD+6W0Y4Apk5U2r8HLdBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-minify/binding-android-arm64@0.132.0': resolution: {integrity: sha512-XYogHG1aSjNEMKWUfWmBWtN9rnpQ2nA4MiecdiAOfofDHTQiU5ybrPH6VvDAtRXf2kr8WtPNX7eenhC3uWFWoA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-minify/binding-android-arm64@0.133.0': + resolution: {integrity: sha512-dnQUJdpOEh/nZfQtvGGN61VcCCcPJ2aCm+ndl8GIA2lk2GpmIBgZ9h+phLVhgUFGt2es+2AQc0xvtK7RFNsViw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-minify/binding-darwin-arm64@0.132.0': resolution: {integrity: sha512-gm/M5dgm7IvA/g9tweMqiFyD15yKrxGUX3myjFP+EYIYVW+RYuvwU5MAIZUOxXY0GnjU1/iRN/JkLhwvhZVsDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-minify/binding-darwin-arm64@0.133.0': + resolution: {integrity: sha512-K6+aXlOlsCcibpTiTitQYNXWGGwea0fEKF/kGHCNB+MNqOLCkdC7wesycaABYcXcyr58DhDoJnVb8E4Hq95iVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-minify/binding-darwin-x64@0.132.0': resolution: {integrity: sha512-s7ecbOJeLccy3nqQlkiq9cV0D0q8j1OyHmxRz22m8qZlcKrc3s4gmhwj5ertipA8ePn3FOXv4azf8b5gatDDug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-minify/binding-darwin-x64@0.133.0': + resolution: {integrity: sha512-BFEXHxYNwThyaO63p1VE5MOOXNGkHsHfkmajOCKXH40TfllTHQenXhpJ9mHDoF7EhaQjArpPjlDY88BuPjhurw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-minify/binding-freebsd-x64@0.132.0': resolution: {integrity: sha512-pdYVNmY9NgKetEWzXlVIUlPm4Z3Gz979nTbZUpHlqpjU/rtulpm0fgROo6rlTk+W0HhZCCZ0Jzy1LBKgn5g3mg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-minify/binding-freebsd-x64@0.133.0': + resolution: {integrity: sha512-oT5dbcXnS/cbpdXCpudAeVg/fqH1XnKhLUE/vkuRTuocjOd/GA2MoNMMhLWUvqNXO0xJnYmo2ISmDxShkItfOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-minify/binding-linux-arm-gnueabihf@0.132.0': resolution: {integrity: sha512-QdV2II2mrbygZO/D+umhb+jMs+kmNO2pvQ+kahY8DN7qZVvaR2CiWBQaAxi3yuI0JvmymcUBEFhRrXsaL/lUqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-minify/binding-linux-arm-musleabihf@0.132.0': - resolution: {integrity: sha512-6OJMBb53luST+xxNSzzg/rRkxMnR4NFQegdu3PCuDEUtP2OEgjmpvvBrHghITpzRsUqnQ/YTl/ItDiLVeoslUA==} + '@oxc-minify/binding-linux-arm-gnueabihf@0.133.0': + resolution: {integrity: sha512-tJ3B+b7DOuTsIMXSmu5xHHCakrBqqcrp4COYd/lelOdDvkbFoDRGnwH91POUOSUEOI/WLzIMkDqAH2SZ3N2jhQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-minify/binding-linux-arm-musleabihf@0.132.0': + resolution: {integrity: sha512-6OJMBb53luST+xxNSzzg/rRkxMnR4NFQegdu3PCuDEUtP2OEgjmpvvBrHghITpzRsUqnQ/YTl/ItDiLVeoslUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-minify/binding-linux-arm-musleabihf@0.133.0': + resolution: {integrity: sha512-XMUHfdilk1KTtOM2vA1bwDso07/wkLm/GgDOO9z/ioxrZoQyjXnJRW665VXa08z2BqEgwHRc1zH9p7s6sKPQbg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -2267,6 +2713,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-minify/binding-linux-arm64-gnu@0.133.0': + resolution: {integrity: sha512-UEff2jopbwJ4SndmxK06uqXrOpwWiJERJPdgDTBywwXP9QgW0p1YkQnBNt4+jK0I/hdLpbbyaCLLuUPKbaU70w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-minify/binding-linux-arm64-musl@0.132.0': resolution: {integrity: sha512-3k8ezEKmxs9Wel4N4vfF/8u764mA57j065P8nB4cU2PO/lLKloN0OA41ynfDUrSM1f5jBuF8+mLOj++aNnu4OA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2274,6 +2727,13 @@ packages: os: [linux] libc: [musl] + '@oxc-minify/binding-linux-arm64-musl@0.133.0': + resolution: {integrity: sha512-yqskeIapQvx7Tu/OLsepLPcGsHGzfYy9PX6gIbhaOHfF+LA2zHBKnKb587FGx+lQjHLQR0llfmoSuXQ6q2EN+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-minify/binding-linux-ppc64-gnu@0.132.0': resolution: {integrity: sha512-vM6jZIxoHoIS5rPb3K3Di0IureL4oU+wOWBy6tLSrjwW2IHqy0442CzO/Ks2U9VCuHV1q0bUGCF0H6AxCEjJHQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2281,6 +2741,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-minify/binding-linux-ppc64-gnu@0.133.0': + resolution: {integrity: sha512-r7PnUNxRB9D/gQjCVeasoieJVUF48n43rvk/jYbGAw9sRfYGoEo/rOs0GyTZU9ttss8HzjBaerAbADbAL8K8vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-minify/binding-linux-riscv64-gnu@0.132.0': resolution: {integrity: sha512-KburrmtWpeZg58uo275QRwy5bbNOXQd1WDI2tGxkY2dJBlO7N5V9+Uthvqn6KI/6RBtjd2T5NO4dCC0fgUxGvw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2288,6 +2755,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-minify/binding-linux-riscv64-gnu@0.133.0': + resolution: {integrity: sha512-omXWC8I9lAMMjQIeadfItP5H4VDAiuU2BiVCtHMH3ktTbFq04sxscZhK4NFUUuw3fApDdXmfd7LW18q0JBHarg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-minify/binding-linux-riscv64-musl@0.132.0': resolution: {integrity: sha512-MnahA2MNEtEdxWdUy24JXkMUNgGPqH285GL2L22Zz7k9ixsguFD+bTbbcR88pNqdb075nazozzv3edF83+azCA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2295,6 +2769,13 @@ packages: os: [linux] libc: [musl] + '@oxc-minify/binding-linux-riscv64-musl@0.133.0': + resolution: {integrity: sha512-LtFA3Hi8LVD/zuiPLKy9Aiz7N1IOj8rRhdXiW38GKQ9mAhj+Ko6IHGcTk2A7yNDA1DZBl7r+Qd4PEGWgVelPPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-minify/binding-linux-s390x-gnu@0.132.0': resolution: {integrity: sha512-VE99UPZyQO2MAG4gLGXzrBumD5PGNaiWe+EakaROGCVbT0YH/d9z2ByYqbdWAMEBiTHjthyZp6UUEFVda+LnpQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2302,6 +2783,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-minify/binding-linux-s390x-gnu@0.133.0': + resolution: {integrity: sha512-rFsPDsT1j3beSInbrFukAAlTg101PcqdVMXDioR9AgJ1180tZ8s8D+pNDpQTRmPd3956mnpAE+Cs77Xoo/QZAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-minify/binding-linux-x64-gnu@0.132.0': resolution: {integrity: sha512-FKxBkYrSAWNF4V6MacAJ/1E2SJobKKQ2CtW6Aq+pLzzEOjgk2SmxnK7I0bATlFH/O70tbTKDzWb17bySGYRcog==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2309,6 +2797,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-minify/binding-linux-x64-gnu@0.133.0': + resolution: {integrity: sha512-xlrtAmDWZI8BEmsaXMYfblWuLIY5UnnRkit1VLkmVDb5ceZRZf4oEXK1QeYf5Z33dT0WK1Ek++P+TL/ZMCpyGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-minify/binding-linux-x64-musl@0.132.0': resolution: {integrity: sha512-W8IqA2XRvg/b6l/f+2SdV45/KKmpmwTabrjiMtpg/wzJU5cmKUoHihtJXPc9NA0Ls9S/oP0wB3PMCRQoNr5J1A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2316,77 +2811,155 @@ packages: os: [linux] libc: [musl] + '@oxc-minify/binding-linux-x64-musl@0.133.0': + resolution: {integrity: sha512-kd36CDkTkZDMNfVceNTSfpWnitE1+GjZmzJCeq8yaxsgvs/MXg8aauI2RgFjElYZIHSMyZku4pQ7Jtl3ZEYI6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-minify/binding-openharmony-arm64@0.132.0': resolution: {integrity: sha512-X1BL65pI9bfOesLdVUcErjbEAUA3qmzjXCwXPCYsFZT7ela7SsK85+sN3m2TJNxmX1mrFKNg5g8bH+d2zHresw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-minify/binding-openharmony-arm64@0.133.0': + resolution: {integrity: sha512-pI38dJBqfkNbFoL/GEarAzGDjKGVCZTdg0a8NKh1PP9GqWleXT6HLtXE4CZ+54e+2u68qVYVBwhbWAiRfwlUZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-minify/binding-wasm32-wasi@0.132.0': resolution: {integrity: sha512-QcIiwBOj+bV5ub5x39Xb+v0boviykxUtVvVJaIEbG/IH97avFzZcBXec8awYlemLDvgG4WKQwr17x7COR5zwFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-minify/binding-wasm32-wasi@0.133.0': + resolution: {integrity: sha512-AkLr+d+LLY4/55J/TrE0srNBUpZPzyU+cygdse7yZ9AhCndryNqe2y6e8naVK0TV7n8lxBd2OGGJAkho6blAkw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-minify/binding-win32-arm64-msvc@0.132.0': resolution: {integrity: sha512-ahFMaa84QVTIROWpLhZcS9jKIv+CXzsJaMmgje7JtlVp1Kaar6tzVCt3EH2aPhSc8RvbGqfmnGdQu/kGwPAZVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-minify/binding-win32-arm64-msvc@0.133.0': + resolution: {integrity: sha512-V92v7397t2073g+mSfaLHnPeoz6hA/1U4JNLeUBP87eWGZgVxDZ2qz3t3wFyYqXGJ/0VoEwdP8yrHLQQ7QzAOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-minify/binding-win32-ia32-msvc@0.132.0': resolution: {integrity: sha512-tpBkLklqOnaYtlIh6gjmL60pP0Kn2hwaw1Fw3sJyIKwdkCPHsOPy/MRgBUpM0a/SeGFbsZRQkHnWfZXS1GTbbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-minify/binding-win32-ia32-msvc@0.133.0': + resolution: {integrity: sha512-2DP5RbG/SSaRVtmuwgTH6Ati4+uuOJjoI88dQnC5hD0zCC90EVDXZSXyJQ5i/OxLE1UAy58Wo2DJot/OrUspzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-minify/binding-win32-x64-msvc@0.132.0': resolution: {integrity: sha512-a69yKrBl2p9O8cdAHbHih56eKhcpKJRVkRu/S+CwCdR2Zsh4nnqYIllF96Lxg3jDjRQNL3t0xZNdYBDG5Vgq+w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-minify/binding-win32-x64-msvc@0.133.0': + resolution: {integrity: sha512-PJ75c6PlBx87tau0W35J43eGCv4wrDmdZ+4ddTZAnGtZqEeCVsLdmDPOEMe2DepogqlSVUF2kGBWtnFUJ5c7Rw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-android-arm-eabi@0.132.0': resolution: {integrity: sha512-KrLaPWa5c9Y7LkW+rKkaUE3y7DBDrQtaf7rlsSDfv6KAHUjgzAIRA761Lrrp6//Yd/Rlie/yEOt9YENCoJnOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm-eabi@0.133.0': + resolution: {integrity: sha512-l/44caGse+VpnY9gx0yvvc5QnnG3yG1FO3KZgYvNL1GZrfK86zIwAOgGEVlxDyRymzrU/KHiblPFpevKOmJmUA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm64@0.132.0': resolution: {integrity: sha512-SThDrSeamB/kG2+NxcJ5/wSLcV6dUqDknrPLqFYQ0ST/55mtBP4M7Q/f3QbubH6aAd11wpzZn/nwbVRSdobOpg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-parser/binding-android-arm64@0.133.0': + resolution: {integrity: sha512-KUHmPMziLBp4u+zbrLdB7iWS7KshuZe+RAp7ELnY9SI9nNXBZ+dp8fiBqWOxhXqn+FQg3a4UcQhwmsJOKV8Jjg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-darwin-arm64@0.132.0': resolution: {integrity: sha512-Lc0f/TYoKBghE5/2Gsv7bLXk+TJZunx2Tf61X8hG4ARXdc8UYI26dCGccFSd1AyFbK3jfaNXtMnupggDbjPXdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.133.0': + resolution: {integrity: sha512-q8dWmnU/8ea2tga9w2f1PinQ5rcMPDUGkF64T189b65YMjUomET4oy5oRldOr4AwOQkneOG/Zttnz1Dvrc62wg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.132.0': resolution: {integrity: sha512-RG2eJIpf7C21z9HSSXFw1bTArdpKe7Y4fwcJTwRq1yCSe1vSavaN9GA1sm9KqzemTLAGVktQ+7qBTGp0vQeUZg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.133.0': + resolution: {integrity: sha512-cOKeIELIB2bJnCKwqx4Rdj+1Lss/U6uCbLxRySZrhyOOQa1flKhwZFjEHRHxk8fU1NKmhK5OnTdPQ4CpjuFuVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-freebsd-x64@0.132.0': resolution: {integrity: sha512-wQIPntPLtJ8NcBpvKPbEv3NqzV6k8eP8tP/jE9Rg8HTg/j7urZGFSsTCPCW5k77Qfw2DM4vRvc9p3I4yq/Shvw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.133.0': + resolution: {integrity: sha512-OpaSv4pW3KgFrMYQxTaS0aOE4T1DQF3qZE/4B6uqqv1KgPWWd4UQhJALi8PJPX1RRV5K7ThKXRfF7qGg2+3l1A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0': resolution: {integrity: sha512-PixKEpeSe3yxQWqNyOCBALRYc72+Tj7ILDofUl3iXo25cVOzLA6jHUhmOINRtWIPh7dbUie3QNeabwaQpZTw6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.133.0': + resolution: {integrity: sha512-JGK1wlGrGwxBIlVSF7KWTX1/ru6BEtf28fRROztDRkLfiW+Kxa4onnriezMIiogfn9hVw2KzYcKiLjkLR2ns8A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.132.0': resolution: {integrity: sha512-sCR+DzGHlyHKnbA2z9zWjTUhIo8Sy0enJl4RDsBwPmkxYynPatpwOAWe8W5127SlW0boqUWHGtr1NWn5UwIhXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.133.0': + resolution: {integrity: sha512-yuZO533Ftonxn/iyoqQzURzLQHMspvsIyfiCSNi1t/ER4eIQaR0SsmUOUm5b/lmSig7IWIUa5/BrbEkAPwcilQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm64-gnu@0.132.0': resolution: {integrity: sha512-sQBix5P2cW+IpzTcCwYxnh9yALrKSIkKJThspBvMGcygSMnbzkSvhN7SfuX1hvBk8y1XEChsdkU3ET0V5DmzUw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2394,6 +2967,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.133.0': + resolution: {integrity: sha512-hvpbqT5pN2rR+3+xtWeizwfR/aZ0vGceg6TqYMl+ToxMpk9/tmnX7kSvQnfEUkoua8mhogzvIKsAkn0wxgblBA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-musl@0.132.0': resolution: {integrity: sha512-WozHg3Kc//8Sk756HXXgMbEAvqtG+Lzb9JOojwQzIGDtN78Az2dLttkb71akWYUF/8IgYfDSlfKh4Uot8is5Vw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2401,6 +2981,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.133.0': + resolution: {integrity: sha512-wJQGamIosQBoJHW9+S5XxrtKRo3eyJxsnS1XCPrqN0LHi8uw1pTqqTfn3t/NVuvbBg7Pumn4ez9Eidgcn0xbEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-ppc64-gnu@0.132.0': resolution: {integrity: sha512-CmX/ulNBOEwWTyVRmcpYKAcAizW6+OjtLJgo7fXoL9OqQvjF4VER8tPomv44vwzfSCy1BHbsB0ZlZYzYJNj4cA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2408,6 +2995,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.133.0': + resolution: {integrity: sha512-Koaz32/O5+abIfrNGdyndgRvdOZ9jEf5/z3Ep9h3h2QWpdDiUQpVwgH0OcMXCs+l9aXxPLtkupqyVig9W6FDKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.132.0': resolution: {integrity: sha512-j9oQS+hM90SdhviNGWbPgT4+Rlq+ac++q/zjgwPD1mVHgxHzATvoRGtDx0sXGmFOQ9J9YkwAhYGb5MAHL6TAsA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2415,6 +3009,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.133.0': + resolution: {integrity: sha512-R4vOjWzxhnNWHnVLeiB6jNuIifdy9vcMXZGPc7StXcxBovI+U2zg1QhZ9o8OjV80oGivs1lX5NfPLzk4IPqlRA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-musl@0.132.0': resolution: {integrity: sha512-bLz+Xi+Agnfmd7kWPEsSVwCn2k4EyIalZkNBcQ0OGIv9rqn8VgCPLNd03tM9mKX/5TdlvDXalz0q71BIrOPNqg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2422,6 +3023,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.133.0': + resolution: {integrity: sha512-iwgBNUTHiMdxARLYuM0SBlnYeb19iw1Ea5M+4ERZupCsBMLArti6FyZ6UfFjJxIiTDr2oW2DGQFxlQVQ/dW9rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-s390x-gnu@0.132.0': resolution: {integrity: sha512-U6t2qbJU0ypTfyj9QV3W1Y6mITDTL8ai/OR6NUn85vyHthOvobKWgXzU4tu0EskSzlpuVFz1g0jFGulDIUKHxQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2429,6 +3037,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.133.0': + resolution: {integrity: sha512-ZwZNo8FZmB/gVfboQl+wXilBigGl+6nQQs+nITOeAP/HcAOjiHl6XZJL9F/KXNEspODQcbjAiyjUbeCJd9a0fA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.132.0': resolution: {integrity: sha512-WcEaSNHFk8yz5YFlQQAlhq6jOFmZBB/RKE7uzhyCIf+pF1Lmv9gUH4221mle2Gd9iHyWT3ySNph8yZgb1xYdWg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2436,6 +3051,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.133.0': + resolution: {integrity: sha512-govCvWx1dBlED3uu4qXctxpRcouu9I8Kn+DBktGCl760JtlGJzc9l/OmPJKlYWSbrRqKkMZehNeZ/4Wfma7uSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-musl@0.132.0': resolution: {integrity: sha512-iQrV4iJzQgRwK3BWRmQl1C3C6g3wYpXN2WLdQdyR+efoUnncdShZAVp9OgcojtlD3MDRbuOMGG3SjxF4fL4nlQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2443,38 +3065,77 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.133.0': + resolution: {integrity: sha512-ssTlpXD5Mq9uCssDJPzlRWqBt4Y7Zzd9i+XZhWmK/9Y6KUIuAxVYTYiI8lxcGWi0+3/Cz4A8q9UrD4NK9Y2j7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-openharmony-arm64@0.132.0': resolution: {integrity: sha512-FWzmUGrZ6GUby4U7WIwcCtab6tdmlTO3xTRRKyb5kjIJVEiaUAT8animUG/nK8ZCA8gkRkPOTId4rl6uTqUmJQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.133.0': + resolution: {integrity: sha512-51aByfXhPtLEdWG4a2Ihdw6cPWV1ei1AarALpFdDP8MLWDLE2NuUMgbo3DERR2Kt8fT/ok1GUvBiLxVGke9uUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-wasm32-wasi@0.132.0': resolution: {integrity: sha512-TlbMppxJI5CjWDes0QaP6G3aneVg1yikBu5QYI+DUShF9WDL66ccgKFNNGmi/Wybtszw6hxwAvv76T4DaPKnHw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.133.0': + resolution: {integrity: sha512-2e16tkKp+wDO2GTAmXfxbBcCmGEaFPIJEIRBBmVKNVXSc8/fJsSIaBGyFTPHM9ST5GNWgJcYIt94rDTks+PLwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.132.0': resolution: {integrity: sha512-RH/NbFjGKqdUAUi7Oh3LQPxUk2hsWFEEQ38HSnbRQT8QjBZFKqL1fMbmsB3N4jy/KPh9iX94+9dmkEMBBbambw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.133.0': + resolution: {integrity: sha512-KPTNDKbxH1cglrqTyVeXHb4Pk4oksz8EcE1/v8zqU7N4UXbiHfA/IwtXZ2U77fnRAWBbgVkl/lZbL7o3hRdejg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.132.0': resolution: {integrity: sha512-JUr4jQY9jxoIB/YTLXr6XofSi5xikj6p5/Ns1h0VOBDT0j1jKU+kMsv2xxv51RwnETcXpA1Yw/9oUAfcqfaqEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.133.0': + resolution: {integrity: sha512-Una1bNYv9zCavQrfnDR9wuZVB3itLjCEH4Oz7i6CwAJN/Xq9b+zbbcxmvdkKvvJt4Ngc/MBmIYlbLo3zS4TQ0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.132.0': resolution: {integrity: sha512-2dapgHpA5X8DSXF4AU36hJWYf6zP0tKjMXFRAZFBD62pkevW/uhFDXoFH9Y/3Fd2EtDrw5ByNnR1wVE9X9y0SQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.133.0': + resolution: {integrity: sha512-kjBhCiOGSYTwDJQuuZa7a94JbP8htWu7J0X1KwH74kV2K5eYf6eyJRYmkpCDvr0XEL8tMxYI4WU1VekblFCLgg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/types@0.132.0': resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxc-project/types@0.95.0': resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} @@ -2484,42 +3145,84 @@ packages: cpu: [arm] os: [android] + '@oxc-transform/binding-android-arm-eabi@0.133.0': + resolution: {integrity: sha512-2A79NBpyBKgHJ0FwgC8D1hzp3x2ujyvqq/kG+M76YyDMMkxLhX6A3vjnAnfEKycOoZxuKhwYu8BF9hKq67ykIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-transform/binding-android-arm64@0.132.0': resolution: {integrity: sha512-sr2BbEHtc5OkAN2nt5BpWGg/MnDkyQKf6tSjaZZ6k7Bb2FOa2CzZDy2pvH6tYdg+Ch/p/OGXXhENFVV9GU7ASw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-transform/binding-android-arm64@0.133.0': + resolution: {integrity: sha512-dynEph/hyoSgBzd2XbNlW37NK97nU6tZMs5jrhObUxSasBV/Gv9THZrWj9AlbWiMXR07WFYE82C9axjntYyBSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-transform/binding-darwin-arm64@0.132.0': resolution: {integrity: sha512-yjL1GbN9Bb1HqjE8CS8NSwoZtDWgUGy43VbuFhmT4LEDx4Ph0guzVAyUKhc2CqqA4/x60qDvcH6QxwrguaqEVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-transform/binding-darwin-arm64@0.133.0': + resolution: {integrity: sha512-4hGgKOG+dZSN3xjcgNWpcihekRG7/YbbAdjyz07yv0HjzA6kdqYAhGrn84374UPO2h6etYJwsCBoM9iJHHvJ8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-transform/binding-darwin-x64@0.132.0': resolution: {integrity: sha512-e3vVXEbNw93aHr3si8eVpUgl+jWF6Ry8RgUihgSxiI+2c/VMxiPsEDghkqPcjujqsMYDRdISWJi23xk+PP72ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-transform/binding-darwin-x64@0.133.0': + resolution: {integrity: sha512-7J11/9PFkznmKuANkCAjt3znV1BcDFXQSgDiBvDxXT3Wm6995/zxrJD5zmo+5XSgY4sm+2V8/ED6ZSD3mKOC5A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-transform/binding-freebsd-x64@0.132.0': resolution: {integrity: sha512-dIhAhkX8/It4IaKI944fN3jmfzunqv2sEG2G4fQdP5/1psycdqUHoVaY23DbpuYRIu4sWAdn/e1zQFP0GMkQOQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-transform/binding-freebsd-x64@0.133.0': + resolution: {integrity: sha512-5EMAO0vzCpUfhn6aSjIUeJeRI2ztevHwSVr/M8sZ2VBYc79UuOfjjMCQ67LtUbgpvQtpBWkzeAHCP3L7JFYmlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-transform/binding-linux-arm-gnueabihf@0.132.0': resolution: {integrity: sha512-eR0dfj1us7DNbGZ6eBdAqWnLZRkLqHFqewSHudX4gV7di3By8E05+M+qsGTB/zq/78Z0BYJeK1zGWu9un6jocw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-transform/binding-linux-arm-gnueabihf@0.133.0': + resolution: {integrity: sha512-z6XT8tmo9sPmCIYaFIxDelBU4wXLwwWMX2VNCMIY6bkQp5r+kRtVXYS3yLbJHMKEhRKvw/g+Z7fO9aadsGGEAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-transform/binding-linux-arm-musleabihf@0.132.0': resolution: {integrity: sha512-naNx0WaV70hKtgQ5LUS/jzRTy6XEQZ1krK7KTFZQLI1mEz+GqLrwsLCqEmtrQ6HcqLhvGvA6GAWfFrc/0mWryA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-transform/binding-linux-arm-musleabihf@0.133.0': + resolution: {integrity: sha512-GQDpEV2VhHG8hT5BviDv+emi9oHYhfv+JJJWROYp+eGgWjiQMp4QZVb6Bu3kwVMzkwy0r200ToA1KThYTq53ug==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-transform/binding-linux-arm64-gnu@0.132.0': resolution: {integrity: sha512-TWk1p0tbtE1tkMEABftfgXhMEfuoz3QieqBtMBXXyijizw/2YKNzbVSndG+vV73cSZgbyfoZ346pmuz0tQMzyw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2527,6 +3230,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-arm64-gnu@0.133.0': + resolution: {integrity: sha512-VstR+NEQAJb80ysWk2vPjEvg0JzwEjKn2hDbC/joa5zGXkCnVVCWgAGG8c6o23S981a7XRpCMcClBgeD1q9H2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-arm64-musl@0.132.0': resolution: {integrity: sha512-LxURDI0Wm2KCQm/3ynNlI+nTgPdfmAfmrl54XPx+gaIqty8S/XWNCCTvLJWaCb0e5eKqnzrcTuhMDOdawqoYIA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2534,6 +3244,13 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-linux-arm64-musl@0.133.0': + resolution: {integrity: sha512-Ec7xJdDrnukgiz20E3iDNzAIgx1XXn8cVVsNNUpgEIAvNlXZaocqlQT8Zalk0Lv3fbkxcJ+9BuWB0ndBRHQtzg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-linux-ppc64-gnu@0.132.0': resolution: {integrity: sha512-eKEeG6SLtj01iDvi5QgMNzyEXt/K2BNWafZ0jGECmvqTWWaO2l4qBxUW+X+sAXp5vZBoT2WO3ZnshvIWXWjtKw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2541,6 +3258,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-ppc64-gnu@0.133.0': + resolution: {integrity: sha512-6YX38grimcigz20eYpyz6e4c9rDKzwK3i+tcDpgwYj0bWreaAOwrABmSmKplPJOorkDVlbT69wPCN+d11irBQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-riscv64-gnu@0.132.0': resolution: {integrity: sha512-Kz6tg1Msra7+2iGV8K5xANLO2SmpP6n+91/Yy+JJh9EagU4hvMm7loReszzz2bwhs6Xs4HPrglxIngMdqnHpXw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2548,6 +3272,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-riscv64-gnu@0.133.0': + resolution: {integrity: sha512-WxMIzItRJR66lgaAyyqj0FFwLMpcuCV9mTFcUMQpIz8+Hey1Enk8xuv+7QpSsqCR5zRlwNr092dsFkz5cbvtrw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-riscv64-musl@0.132.0': resolution: {integrity: sha512-dtUSp80ElrxUhfBNmFWGkFQQ51j3tRoZkKBXxEWh+hb+S6bbEdZCW/VuCYo/gCTH3DywwyTeWiG+dtZfJiHKvg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2555,6 +3286,13 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-linux-riscv64-musl@0.133.0': + resolution: {integrity: sha512-+x6dnO87986rjVNjcF0tg8wVS0e/SH8nzLa/X0Wsh7jtEniN7buvR8iqZm8pnsfaZ8DH5F4GCSZpoPRrd9jJ6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-linux-s390x-gnu@0.132.0': resolution: {integrity: sha512-9qVyCbYSs8dwVPpqKKWxuUAnLJ1+LyC5A4oNMZTzymRhuQr3coqAP/XWfJ8LlhQqI9GvhK0SWCOK0iM3HFUAnA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2562,6 +3300,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-s390x-gnu@0.133.0': + resolution: {integrity: sha512-oEyQudXIwWM/+v0vZzPbAi25YMWyvjtQYYjuSrhMEQwe7ZEMDXscX7U1j6alrVdZq2DtCMeror3X/Dv7p/JUwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-x64-gnu@0.132.0': resolution: {integrity: sha512-dUtJkDCYndDaxcuiSMyRoSb7sXmTbcJ61rDsUjIakghP6BkKwH57lyHYvSUhT1ZswXWwCjf3ksxlT8nA0iU6ag==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2569,6 +3314,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-transform/binding-linux-x64-gnu@0.133.0': + resolution: {integrity: sha512-G8P/OadKTbyUHz5TK63sDDtUHwn2SXG/o0oGo4GGTzBu70xmUSN5/ZUgpyl6ypAmbshoyw8nC7+msb3BjzHxaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-transform/binding-linux-x64-musl@0.132.0': resolution: {integrity: sha512-I7BkkktnrriiO7o1dF3RDgKZoSmFKX9IE0W2LE1WdfmpZcAa3fbv5BW6oVbzk40iD29hWSP69A65WT9l6dxuzg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2576,35 +3328,71 @@ packages: os: [linux] libc: [musl] + '@oxc-transform/binding-linux-x64-musl@0.133.0': + resolution: {integrity: sha512-Oi/fyOzZ+aytmmsRND5pGgvux4n++v9cG4qNFiXj7qFwSqBKWZHBq7cJLXqbH1I81pyI3kvU1Za+1qk3afXuwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-transform/binding-openharmony-arm64@0.132.0': resolution: {integrity: sha512-yiXaRYqgySJguURNZUFLDzSI1NTkP1jJKrowr8lQCKwY5N8DsESbQJ1RpSlEbeXGiy201puA+QC2fdr+ywQM/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-transform/binding-openharmony-arm64@0.133.0': + resolution: {integrity: sha512-/ZElgq+/tcga27X2G2AUpxcYX0baX94Gz658w6Zz2P+6Kr06bfYSrdtC0P7oPrbu3Gy/6kpiSoJPgZy8R2IjYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-transform/binding-wasm32-wasi@0.132.0': resolution: {integrity: sha512-KNago0Mv+zl2yl5hK2G9V4Yb7Tgpn+z6lgzgaHXkGp7S+iuUtN3av+QqPCD/J+Odq6EjjyXJrFPfmyjbXXbf4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-transform/binding-wasm32-wasi@0.133.0': + resolution: {integrity: sha512-GANcoEa8Nzza7saxdb4qWO24U6jk4nK6G+g87lGp8TTU45CUvWf1Igdze2+NrebgiwOy6F1/h6Esag4DM3JTtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@oxc-transform/binding-win32-arm64-msvc@0.132.0': resolution: {integrity: sha512-3fprECrLHwPP809a1SRzszDxp8Fpp8IOg0V2EO49wS+3JmRFOo090h5c37faZvym5VnRZ12DH2tkT6ZVXwlOsA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-transform/binding-win32-arm64-msvc@0.133.0': + resolution: {integrity: sha512-2+uDo/+ZvGQu10J8xryg/l5PdBt2vXPtf+0aIosVKJavqCaKcBDdo95OUaEulx0bqvoytAQ4yyz2gcPZ40mjcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-transform/binding-win32-ia32-msvc@0.132.0': resolution: {integrity: sha512-n616QqZ3hXasHytVoFjo6pLzIfo6hQwBEir0kOcaObKaAw0ZbncIe1h5a6IMnCOJGLP30WwnhwLW20tIV78MAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-transform/binding-win32-ia32-msvc@0.133.0': + resolution: {integrity: sha512-zpPIZ1S3JHmSEFyyGyPYCwhOiNLyfaPifYxK8BQY21JXyHglu/wUr3/ESFrXb+XegEy/iBlWbzr3FzPtcq1MUw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-transform/binding-win32-x64-msvc@0.132.0': resolution: {integrity: sha512-P7A4Cz/0C0Oxa2zH/oCruzA/5EHr5RRz0x6KXYz3wwhS+dFqIBxP9yo8FKjXhKXHRKa+M+QHo+bqYiqqlVsEQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-transform/binding-win32-x64-msvc@0.133.0': + resolution: {integrity: sha512-cADrfLvc/VeyvpvQS+t5ktqfyqyyGANZC5NHp++JAElacfXqq/+k8bYkjqMWzNZ3HxkJtL1qDHfZZCA9+4hlSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@package-json/types@0.0.12': resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} @@ -4011,6 +4799,9 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + caniuse-api@4.0.0: + resolution: {integrity: sha512-B0hQ1OLyJuHTQSOWXvwibWqM6DCoqJdvBA6X1S/53bd4XU7LJ1yurIPlrsouol3mw1jh9pGI4ivubSpmJeIqCA==} + caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} @@ -4229,18 +5020,36 @@ packages: peerDependencies: postcss: ^8.5.13 + cssnano-preset-default@8.0.2: + resolution: {integrity: sha512-+jQAqIKCqMmBjZs7741XkilU93ITZ/EW8gjAkMmujdCzfDkfjrDBv2VqkSu29Fzeig/0rZ3S9IAwfPLlmXEUfQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + cssnano-utils@5.0.3: resolution: {integrity: sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + cssnano-utils@6.0.1: + resolution: {integrity: sha512-zk65GIxA8tCjqVk7nTm1mE+ZKxtnxAvU5JSUaBLXbAr3ZF7IOvz3fbPOnEDvZKhnS7GOIitXTS5BgehLzNoc8Q==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + cssnano@7.1.9: resolution: {integrity: sha512-uPR75+5Dk/WJ/YSPR1/YDHdwMM9c5FsaARljfKWgeCKLKOtJ0we21xy/RcCjn53fZnD/f6yYEIZ8pu18+GnbNQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + cssnano@8.0.2: + resolution: {integrity: sha512-K+a76gA1v0/CsYgcsE95HGGyIuPKxpQSetwSwz4nHEM8fFXqSkzq2JzEXFL8v5+CCjxzVVVhPcTK3Oo8SaF/xA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -5718,13 +6527,26 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nuxt@3.21.7: - resolution: {integrity: sha512-S3QBFlFnZ+e0j2qw4h8Fzc8qzZxRaOaUl3/6zgy3oIrj8l65NmnW7lhMhbCGA44PLIEcukNTvx9jZ4K7IPNV2Q==} - engines: {node: ^20.19.0 || >=22.12.0} + nuxt@3.21.7: + resolution: {integrity: sha512-S3QBFlFnZ+e0j2qw4h8Fzc8qzZxRaOaUl3/6zgy3oIrj8l65NmnW7lhMhbCGA44PLIEcukNTvx9jZ4K7IPNV2Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@parcel/watcher': ^2.1.0 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + peerDependenciesMeta: + '@parcel/watcher': + optional: true + '@types/node': + optional: true + + nuxt@4.4.8: + resolution: {integrity: sha512-r/DGE4cNkEDclOw9tbJ18zqu+ix3me+7QCfumPdl5lBXGWgCajskzuq/HzDkHKfIZsn7ACVEjMLRNA2teh++bQ==} + engines: {node: ^22.12.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: '@parcel/watcher': ^2.1.0 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@types/node': '>=18.12.0' peerDependenciesMeta: '@parcel/watcher': optional: true @@ -5808,14 +6630,26 @@ packages: resolution: {integrity: sha512-7h3fOlgDwkIYxxKfGwCNejaLhH90Pvx+fttdPN7nRbWHxm6QSYcxW3IKjfxQVUeg+z1X2HZdOSY3rHkVqgxH1g==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-minify@0.133.0: + resolution: {integrity: sha512-6bNsYU+5WNIaNHB16zHnL24cUaJuKiPzUvjENoMale3+U8ZBMbGYgdgt//frx0ge7UcgEGIpqtukGGNPT0kxfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.132.0: resolution: {integrity: sha512-+0LAPHaqtfQlvWdpaAa09SmOaZZgP8C552xosEkGJ4+ruEwP1Vgx+sqBgcBCNfR6KDCmagGOZTde8wmAvcI/Hg==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.133.0: + resolution: {integrity: sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-transform@0.132.0: resolution: {integrity: sha512-DmP0+4kzpXoMvv08qPCD4aI6mAIzrEq15Yt9e6wXCNtOz6jEDHPpueusDa2/pvjRAqtNV37YxUUeX7cfCI4dpA==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-transform@0.133.0: + resolution: {integrity: sha512-9lt2b+hkG6yqe0fUDMHhMk7rgI9uTjNxU9wauQiYnHzc4kZI8JP/OhBqXTIJQTrqRJ8CkSH3O5AhQ13ke28yNg==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-walker@1.0.0: resolution: {integrity: sha512-eMsHflAGfOskpWxtp9xP/f5b96XLEU8ifTd2gOOCkdux9HMxKGy5S1ru0Gh1B3aPu+YbfmWUUVkcb7MrZz3XyQ==} peerDependencies: @@ -5948,72 +6782,144 @@ packages: peerDependencies: postcss: ^8.5.13 + postcss-colormin@8.0.1: + resolution: {integrity: sha512-qBY4ABQ6d8/mk5RRZHwMllrZMxeMey3azVY2dZUEk+RgiUC4ARdPR3/AITzNqqKTbvW/3y/MJKinDrzwqn8RDQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-convert-values@7.0.12: resolution: {integrity: sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-convert-values@8.0.1: + resolution: {integrity: sha512-IdOSIX3BzfMvCc1TAHIha2gfy17xnb5vfML8e2BIKARnFOghksESfaSAB/3CXgyLfMozZAbTRPVQF5dbuKOidw==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-discard-comments@7.0.8: resolution: {integrity: sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-discard-comments@8.0.1: + resolution: {integrity: sha512-FDvzm3tXlEsQBO2XQgnta5ugsAqwBrgWH+j5QgXpegEIDYA0VPnZg2aP7LtmWtC49POskeIhXesFiU/k3NyFHA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-discard-duplicates@7.0.4: resolution: {integrity: sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-discard-duplicates@8.0.1: + resolution: {integrity: sha512-stTDXkI8YkCUfADurQhp03oq5ynsgSx6Qrw5B1swds6oTHtAeOZ9I0SHGK8cY/VpWUsIYFDWMs3IWf9jIEfFvA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-discard-empty@7.0.3: resolution: {integrity: sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-discard-empty@8.0.1: + resolution: {integrity: sha512-Zv4fM1Yfhk71tbt6gfiptbL6jDHi+7apSnaMeaO9n1uET+1embrXQw5m93Zp5x28UyQSuv+AVkFY193jdwZ33w==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-discard-overridden@7.0.3: resolution: {integrity: sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-discard-overridden@8.0.1: + resolution: {integrity: sha512-ykt4fvrC7yYGzbxKyqBVjDCbsjF/11JgWK8enrdkobRyqqEtb/uDUCbKOGdvrK8X7BrShW8Lv5cCRNbdkNHGkQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-merge-longhand@7.0.7: resolution: {integrity: sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-merge-longhand@8.0.1: + resolution: {integrity: sha512-huTfSYgQ13O81SFvAuOi7GWnO48vvybjj3xF+X3qUoPjzvvaLpJH5DcUqqXcwOEulZUcvaV4s0V9WtWs+IAQPA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-merge-rules@7.0.11: resolution: {integrity: sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-merge-rules@8.0.1: + resolution: {integrity: sha512-o3rk4UpnPNg469tklYwbR/NtvKc/f/wJiVDTnNQ/EFPw/LeiPOHUCvV1GIBQIZHGrBAYdPjToK6a+ojYprsrxQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-minify-font-values@7.0.3: resolution: {integrity: sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-minify-font-values@8.0.1: + resolution: {integrity: sha512-L8Nzs/PRlBSPrLdY/7rAiU5ZN5800+2J/4LRbfyG8SJnPljmgMaXVmQiCklvRS+yObfVRNtvmk/Ean/eoYcSeg==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-minify-gradients@7.0.5: resolution: {integrity: sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-minify-gradients@8.0.1: + resolution: {integrity: sha512-qf+4s/hZMqTwpWN2teqf6+1yvR/SZK5HgHqXYuACeJXV7ABe7AXtBEomgxagUzcN4bSnmqBh5vnIml0dYqykYg==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-minify-params@7.0.9: resolution: {integrity: sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-minify-params@8.0.1: + resolution: {integrity: sha512-L0h3H59deFfFg0wQN1NVaS/8E/LfGvaMuZKGO7siwlG995zo3OshtQyRkqKdVqcBwAORBvZ1nDZrKPLRapYkQw==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-minify-selectors@7.1.2: resolution: {integrity: sha512-aQtrEWKwqafNlExcKHQvPGsXR2+vlUqqJtf5XsCQcgsSb5PL4wlujWBYDJuWsP4UnQX1YHDHU8qRlD+1PzTQ+Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-minify-selectors@8.0.2: + resolution: {integrity: sha512-3icdxc/zght5UAizdwqZBDE2KOWHf1jMQCxET6iLACeNlRxfTPyXS0/COpGk8CQ2cECyaEKTRUd/i/k8Gxmz4g==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-nested@7.0.2: resolution: {integrity: sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==} engines: {node: '>=18.0'} @@ -6026,72 +6932,144 @@ packages: peerDependencies: postcss: ^8.5.13 + postcss-normalize-charset@8.0.1: + resolution: {integrity: sha512-xzqr36F8UeIZOvOHsf3aul+RVJCADvSwuwpMLgizqKjisHZpBfztgW0XFLBfJvz9pJgaStaOXAtGb0zLqT6B0w==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-display-values@7.0.3: resolution: {integrity: sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-display-values@8.0.1: + resolution: {integrity: sha512-ZDWOijOK1FFMlpgiQCUO9fCNKd7HJ9L7z9HWEq4iyubnUFWzdTSwm/LcrMbNW6iZ1oAtqeLYA0WA3xHszOI08g==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-positions@7.0.4: resolution: {integrity: sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-positions@8.0.1: + resolution: {integrity: sha512-uuivan2poSqbE48ST4do20dGaFUeXey9/H8rhHzoyVHB2I6BmkoVLZ/C9+BRjUlpaAFYVOoDY7epkiidzaYbvA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-repeat-style@7.0.4: resolution: {integrity: sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-repeat-style@8.0.1: + resolution: {integrity: sha512-q2hq5fmKxk29K6DjKA3nZ17Q2dtjhLYFNmFweKALmooUqx6UWAHF1bBoWTu/EqlJ88josb82A/J0Atj9LJUmpQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-string@7.0.3: resolution: {integrity: sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-string@8.0.1: + resolution: {integrity: sha512-+Wf+kQJhm1WgSGEAuUaswE9rdpR9QbrKRVemcVHs6rhOoOTVIdAbgaicftfYA6vLM346P8onRzkEVbFN29ktKQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-timing-functions@7.0.3: resolution: {integrity: sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-timing-functions@8.0.1: + resolution: {integrity: sha512-W8/tvwRlm3T+yjGkg0IRTF4bvHj0vILYr/LOogCrJKHz2ey2HFRwfsAA8Bk9N4BGR7z7WmmDu/KzzwhJ6FoGPQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-unicode@7.0.9: resolution: {integrity: sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-unicode@8.0.1: + resolution: {integrity: sha512-Ad0YHNRBp4WHEOYUM/4wL/8MoL2fimEF8se/0q+Rt/owMzYpbxsypC1P8fN/oluwoRmRKdNVX7X2oycEobPWcQ==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-url@7.0.3: resolution: {integrity: sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-url@8.0.1: + resolution: {integrity: sha512-tkYcip6pCDY806xuxpJYqMW2M3/623jzGFJmz3m5Us47q8P28+gbRZxaea3Rr/CmwwLUiVlh+BTGYwQ6gvaP8A==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-normalize-whitespace@7.0.3: resolution: {integrity: sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-normalize-whitespace@8.0.1: + resolution: {integrity: sha512-XzORadNfSrKWDZZpgAEHPKINKx8r9r9RIfE9c70g/HThdpbmPHhDYCodHSVESDxmKeySAYw1p4liuBCf7j6LyA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-ordered-values@7.0.4: resolution: {integrity: sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-ordered-values@8.0.1: + resolution: {integrity: sha512-OLXq5lR1yk3KWQ1FPK6aWjFFdktHE9f9kb8cnt4LmIw7w30DnzgD9+sOVYJc5HenkWCX8i1MJhhFwmqc/GYqLg==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-reduce-initial@7.0.9: resolution: {integrity: sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-reduce-initial@8.0.1: + resolution: {integrity: sha512-+aQsR6+61KRoIfcFNLP3v9RM7+0iYOTtPnjl1wr6JqMW1zx6S+t2ktHRefXwacFdHIDj5+ETG0KY7K3+SGQ4Nw==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-reduce-transforms@7.0.3: resolution: {integrity: sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-reduce-transforms@8.0.1: + resolution: {integrity: sha512-x71slHVykiFi5RuKEXM0wgYpY2PngC78x6R8TnZhHF3lhqt+u/w3MGwYLX+2t5O87ssRiMfEAhQH+3J4QwVzCw==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-selector-parser@7.1.4: resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} engines: {node: '>=4'} @@ -6102,12 +7080,24 @@ packages: peerDependencies: postcss: ^8.5.13 + postcss-svgo@8.0.1: + resolution: {integrity: sha512-HpnvWii7W0/FPrsejJa6ZTi0kNtTJP/Iba7CUMPX0xPV6QpnndOp+SDP74tFtgjA2cYKYNWJPOlmLXMsvi/9yA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-unique-selectors@7.0.7: resolution: {integrity: sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} peerDependencies: postcss: ^8.5.13 + postcss-unique-selectors@8.0.1: + resolution: {integrity: sha512-+xvKI5+/Cl8yYQwxDV39Uhuc4WV951xngFvPPjiPj2NIbIfm6vbbRTXblyw0FioLkIoGlw+7qUcY1h2YhaZYgw==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -6664,6 +7654,12 @@ packages: peerDependencies: postcss: ^8.5.13 + stylehacks@8.0.1: + resolution: {integrity: sha512-Gv095oTD0N+BdJALNFDsxZpETHZLTxbOl5RyIO7y6VAE6sR3z0MnV3Nix7N0IATNldNTrkvSASp2KR1Yt526HA==} + engines: {node: ^22.11.0 || ^24.11.0 || >=26.0} + peerDependencies: + postcss: ^8.5.15 + stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} @@ -6936,6 +7932,9 @@ packages: resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} engines: {node: ^20.19.0 || >=22.12.0} + unrouting@0.1.7: + resolution: {integrity: sha512-+0hfD+CVWtD636rc5Fn9VEjjTEDhdqgMpbwAuVoUmydSHDaMNiFW93SJG4LV++RoGSEAyvQN5uABAscYpDphpQ==} + unrs-resolver@1.12.2: resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} @@ -7096,6 +8095,37 @@ packages: vue-tsc: optional: true + vite-plugin-checker@0.14.4: + resolution: {integrity: sha512-Tw0U9UgHIRiZ+Yoe4Gh0RrYoBiCVmO9j4tomVdYr0KUjUsqXMPhqW8ouoSWmOzGp5Iimipbl3bNXZcK7OeP7Qg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@biomejs/biome': '>=2.4.12' + eslint: '>=9.39.4' + meow: ^13.2.0 || ^14.0.0 + optionator: ^0.9.4 + oxlint: '>=1' + stylelint: '>=16.26.1' + typescript: '*' + vite: '>=5.4.21' + vue-tsc: ~2.2.10 || ^3.0.0 + peerDependenciesMeta: + '@biomejs/biome': + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + oxlint: + optional: true + stylelint: + optional: true + typescript: + optional: true + vue-tsc: + optional: true + vite-plugin-inspect@11.4.1: resolution: {integrity: sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==} engines: {node: '>=14'} @@ -8393,6 +9423,44 @@ snapshots: - magicast - supports-color + '@nuxt/cli@3.36.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.3)': + dependencies: + '@bomb.sh/tab': 0.0.16(cac@6.7.14)(citty@0.2.2) + '@clack/prompts': 1.6.0 + c12: 3.3.4(magicast@0.5.3) + citty: 0.2.2 + confbox: 0.2.4 + consola: 3.4.2 + debug: 4.4.3 + defu: 6.1.7 + exsolve: 1.0.8 + fuse.js: 7.4.2 + fzf: 0.5.2 + giget: 3.3.0 + jiti: 2.7.0 + listhen: 1.10.0(srvx@0.11.17) + nypm: 0.6.7 + ofetch: 1.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.1.0 + pkg-types: 2.3.1 + scule: 1.3.0 + semver: 7.8.5 + srvx: 0.11.17 + std-env: 4.1.0 + tinyclip: 0.1.14 + tinyexec: 1.2.4 + ufo: 1.6.4 + youch: 4.1.1 + optionalDependencies: + '@nuxt/schema': 4.4.8 + transitivePeerDependencies: + - cac + - commander + - magicast + - supports-color + '@nuxt/devalue@2.0.2': {} '@nuxt/devtools-kit@2.6.4(magicast@0.3.5)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': @@ -8683,10 +9751,10 @@ snapshots: - uploadthing - xml2js - '@nuxt/nitro-server@3.21.7(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(typescript@5.9.3)': + '@nuxt/nitro-server@4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 3.21.7(magicast@0.5.3) + '@nuxt/kit': 4.4.8(magicast@0.5.3) '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3)) '@vue/shared': 3.5.38 consola: 3.4.2 @@ -8700,11 +9768,11 @@ snapshots: impound: 1.1.5 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)) - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) + nitropack: 2.13.4(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) + nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) + nypm: 0.6.7 ohash: 2.0.11 pathe: 2.0.3 - pkg-types: 2.3.1 rou3: 0.8.1 std-env: 4.1.0 ufo: 1.6.4 @@ -8713,6 +9781,8 @@ snapshots: vue: 3.5.38(typescript@5.9.3) vue-bundle-renderer: 2.2.0 vue-devtools-stub: 0.1.0 + optionalDependencies: + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -8759,6 +9829,14 @@ snapshots: pkg-types: 2.3.1 std-env: 4.1.0 + '@nuxt/schema@4.4.8': + dependencies: + '@vue/shared': 3.5.38 + defu: 6.1.7 + pathe: 2.0.3 + pkg-types: 2.3.1 + std-env: 4.1.0 + '@nuxt/telemetry@2.8.0(@nuxt/kit@3.21.7(magicast@0.5.3))': dependencies: '@nuxt/kit': 3.21.7(magicast@0.5.3) @@ -8768,6 +9846,15 @@ snapshots: rc9: 3.0.1 std-env: 4.1.0 + '@nuxt/telemetry@2.8.0(@nuxt/kit@4.4.8(magicast@0.5.3))': + dependencies: + '@nuxt/kit': 4.4.8(magicast@0.5.3) + citty: 0.2.2 + consola: 3.4.2 + ofetch: 2.0.0-alpha.3 + rc9: 3.0.1 + std-env: 4.1.0 + '@nuxt/test-utils@3.17.2(@playwright/test@1.60.0)(@types/node@24.7.2)(@vue/test-utils@2.4.6)(jiti@2.7.0)(jsdom@27.0.1)(magicast@0.5.3)(playwright-core@1.60.0)(terser@5.48.0)(typescript@5.9.3)(vitest@4.1.8)(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.7(magicast@0.5.3) @@ -8880,30 +9967,27 @@ snapshots: - vue-tsc - yaml - '@nuxt/vite-builder@3.21.7(@types/node@24.7.2)(eslint@9.39.4(jiti@2.7.0))(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0)': + '@nuxt/vite-builder@4.4.8(5e770e72ca055b8ebf51d504a3c88ac6)': dependencies: - '@nuxt/kit': 3.21.7(magicast@0.5.3) + '@nuxt/kit': 4.4.8(magicast@0.5.3) '@rollup/plugin-replace': 6.0.3(rollup@4.62.2) '@vitejs/plugin-vue': 6.0.7(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.5(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) autoprefixer: 10.5.0(postcss@8.5.15) consola: 3.4.2 - cssnano: 7.1.9(postcss@8.5.15) + cssnano: 8.0.2(postcss@8.5.15) defu: 6.1.7 escape-string-regexp: 5.0.0 exsolve: 1.0.8 - externality: 1.0.2 get-port-please: 3.2.0 jiti: 2.7.0 knitwork: 1.3.0 magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0) nypm: 0.6.7 - ohash: 2.0.11 pathe: 2.0.3 - perfect-debounce: 2.1.0 pkg-types: 2.3.1 postcss: 8.5.15 seroval: 1.5.4 @@ -8912,12 +9996,13 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) vite-node: 5.3.0(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - vite-plugin-checker: 0.13.0(eslint@9.39.4(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + vite-plugin-checker: 0.14.4(eslint@9.39.4(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) vue: 3.5.38(typescript@5.9.3) vue-bundle-renderer: 2.2.0 optionalDependencies: - rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) - rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2) transitivePeerDependencies: - '@biomejs/biome' - '@types/node' @@ -8938,8 +10023,6 @@ snapshots: - terser - tsx - typescript - - vls - - vti - vue-tsc - yaml @@ -8948,51 +10031,99 @@ snapshots: '@oxc-minify/binding-android-arm-eabi@0.132.0': optional: true + '@oxc-minify/binding-android-arm-eabi@0.133.0': + optional: true + '@oxc-minify/binding-android-arm64@0.132.0': optional: true + '@oxc-minify/binding-android-arm64@0.133.0': + optional: true + '@oxc-minify/binding-darwin-arm64@0.132.0': optional: true + '@oxc-minify/binding-darwin-arm64@0.133.0': + optional: true + '@oxc-minify/binding-darwin-x64@0.132.0': optional: true + '@oxc-minify/binding-darwin-x64@0.133.0': + optional: true + '@oxc-minify/binding-freebsd-x64@0.132.0': optional: true + '@oxc-minify/binding-freebsd-x64@0.133.0': + optional: true + '@oxc-minify/binding-linux-arm-gnueabihf@0.132.0': optional: true + '@oxc-minify/binding-linux-arm-gnueabihf@0.133.0': + optional: true + '@oxc-minify/binding-linux-arm-musleabihf@0.132.0': optional: true + '@oxc-minify/binding-linux-arm-musleabihf@0.133.0': + optional: true + '@oxc-minify/binding-linux-arm64-gnu@0.132.0': optional: true + '@oxc-minify/binding-linux-arm64-gnu@0.133.0': + optional: true + '@oxc-minify/binding-linux-arm64-musl@0.132.0': optional: true + '@oxc-minify/binding-linux-arm64-musl@0.133.0': + optional: true + '@oxc-minify/binding-linux-ppc64-gnu@0.132.0': optional: true + '@oxc-minify/binding-linux-ppc64-gnu@0.133.0': + optional: true + '@oxc-minify/binding-linux-riscv64-gnu@0.132.0': optional: true + '@oxc-minify/binding-linux-riscv64-gnu@0.133.0': + optional: true + '@oxc-minify/binding-linux-riscv64-musl@0.132.0': optional: true + '@oxc-minify/binding-linux-riscv64-musl@0.133.0': + optional: true + '@oxc-minify/binding-linux-s390x-gnu@0.132.0': optional: true + '@oxc-minify/binding-linux-s390x-gnu@0.133.0': + optional: true + '@oxc-minify/binding-linux-x64-gnu@0.132.0': optional: true + '@oxc-minify/binding-linux-x64-gnu@0.133.0': + optional: true + '@oxc-minify/binding-linux-x64-musl@0.132.0': optional: true + '@oxc-minify/binding-linux-x64-musl@0.133.0': + optional: true + '@oxc-minify/binding-openharmony-arm64@0.132.0': optional: true + '@oxc-minify/binding-openharmony-arm64@0.133.0': + optional: true + '@oxc-minify/binding-wasm32-wasi@0.132.0': dependencies: '@emnapi/core': 1.10.0 @@ -9000,63 +10131,127 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-minify/binding-wasm32-wasi@0.133.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-minify/binding-win32-arm64-msvc@0.132.0': optional: true + '@oxc-minify/binding-win32-arm64-msvc@0.133.0': + optional: true + '@oxc-minify/binding-win32-ia32-msvc@0.132.0': optional: true + '@oxc-minify/binding-win32-ia32-msvc@0.133.0': + optional: true + '@oxc-minify/binding-win32-x64-msvc@0.132.0': optional: true + '@oxc-minify/binding-win32-x64-msvc@0.133.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.132.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.133.0': + optional: true + '@oxc-parser/binding-android-arm64@0.132.0': optional: true + '@oxc-parser/binding-android-arm64@0.133.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.132.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.133.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.132.0': optional: true + '@oxc-parser/binding-darwin-x64@0.133.0': + optional: true + '@oxc-parser/binding-freebsd-x64@0.132.0': optional: true + '@oxc-parser/binding-freebsd-x64@0.133.0': + optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.132.0': optional: true + '@oxc-parser/binding-linux-arm-gnueabihf@0.133.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.132.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.133.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.132.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.133.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.132.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.133.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.132.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.133.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.132.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.133.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.132.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.133.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.132.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.133.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.132.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.133.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.132.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.133.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.132.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.133.0': + optional: true + '@oxc-parser/binding-wasm32-wasi@0.132.0': dependencies: '@emnapi/core': 1.10.0 @@ -9064,67 +10259,133 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-parser/binding-wasm32-wasi@0.133.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.132.0': optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.133.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.132.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.133.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.132.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.133.0': + optional: true + '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.133.0': {} + '@oxc-project/types@0.95.0': {} '@oxc-transform/binding-android-arm-eabi@0.132.0': optional: true + '@oxc-transform/binding-android-arm-eabi@0.133.0': + optional: true + '@oxc-transform/binding-android-arm64@0.132.0': optional: true + '@oxc-transform/binding-android-arm64@0.133.0': + optional: true + '@oxc-transform/binding-darwin-arm64@0.132.0': optional: true + '@oxc-transform/binding-darwin-arm64@0.133.0': + optional: true + '@oxc-transform/binding-darwin-x64@0.132.0': optional: true + '@oxc-transform/binding-darwin-x64@0.133.0': + optional: true + '@oxc-transform/binding-freebsd-x64@0.132.0': optional: true + '@oxc-transform/binding-freebsd-x64@0.133.0': + optional: true + '@oxc-transform/binding-linux-arm-gnueabihf@0.132.0': optional: true + '@oxc-transform/binding-linux-arm-gnueabihf@0.133.0': + optional: true + '@oxc-transform/binding-linux-arm-musleabihf@0.132.0': optional: true + '@oxc-transform/binding-linux-arm-musleabihf@0.133.0': + optional: true + '@oxc-transform/binding-linux-arm64-gnu@0.132.0': optional: true + '@oxc-transform/binding-linux-arm64-gnu@0.133.0': + optional: true + '@oxc-transform/binding-linux-arm64-musl@0.132.0': optional: true + '@oxc-transform/binding-linux-arm64-musl@0.133.0': + optional: true + '@oxc-transform/binding-linux-ppc64-gnu@0.132.0': optional: true + '@oxc-transform/binding-linux-ppc64-gnu@0.133.0': + optional: true + '@oxc-transform/binding-linux-riscv64-gnu@0.132.0': optional: true + '@oxc-transform/binding-linux-riscv64-gnu@0.133.0': + optional: true + '@oxc-transform/binding-linux-riscv64-musl@0.132.0': optional: true + '@oxc-transform/binding-linux-riscv64-musl@0.133.0': + optional: true + '@oxc-transform/binding-linux-s390x-gnu@0.132.0': optional: true + '@oxc-transform/binding-linux-s390x-gnu@0.133.0': + optional: true + '@oxc-transform/binding-linux-x64-gnu@0.132.0': optional: true + '@oxc-transform/binding-linux-x64-gnu@0.133.0': + optional: true + '@oxc-transform/binding-linux-x64-musl@0.132.0': optional: true + '@oxc-transform/binding-linux-x64-musl@0.133.0': + optional: true + '@oxc-transform/binding-openharmony-arm64@0.132.0': optional: true + '@oxc-transform/binding-openharmony-arm64@0.133.0': + optional: true + '@oxc-transform/binding-wasm32-wasi@0.132.0': dependencies: '@emnapi/core': 1.10.0 @@ -9132,15 +10393,31 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-transform/binding-wasm32-wasi@0.133.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@oxc-transform/binding-win32-arm64-msvc@0.132.0': optional: true + '@oxc-transform/binding-win32-arm64-msvc@0.133.0': + optional: true + '@oxc-transform/binding-win32-ia32-msvc@0.132.0': optional: true + '@oxc-transform/binding-win32-ia32-msvc@0.133.0': + optional: true + '@oxc-transform/binding-win32-x64-msvc@0.132.0': optional: true + '@oxc-transform/binding-win32-x64-msvc@0.133.0': + optional: true + '@package-json/types@0.0.12': {} '@parcel/watcher-android-arm64@2.5.6': @@ -10684,6 +11961,11 @@ snapshots: lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 + caniuse-api@4.0.0: + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001799 + caniuse-lite@1.0.30001799: {} chai@6.2.2: {} @@ -10898,16 +12180,59 @@ snapshots: postcss-svgo: 7.1.3(postcss@8.5.15) postcss-unique-selectors: 7.0.7(postcss@8.5.15) + cssnano-preset-default@8.0.2(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + cssnano-utils: 6.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-calc: 10.1.1(postcss@8.5.15) + postcss-colormin: 8.0.1(postcss@8.5.15) + postcss-convert-values: 8.0.1(postcss@8.5.15) + postcss-discard-comments: 8.0.1(postcss@8.5.15) + postcss-discard-duplicates: 8.0.1(postcss@8.5.15) + postcss-discard-empty: 8.0.1(postcss@8.5.15) + postcss-discard-overridden: 8.0.1(postcss@8.5.15) + postcss-merge-longhand: 8.0.1(postcss@8.5.15) + postcss-merge-rules: 8.0.1(postcss@8.5.15) + postcss-minify-font-values: 8.0.1(postcss@8.5.15) + postcss-minify-gradients: 8.0.1(postcss@8.5.15) + postcss-minify-params: 8.0.1(postcss@8.5.15) + postcss-minify-selectors: 8.0.2(postcss@8.5.15) + postcss-normalize-charset: 8.0.1(postcss@8.5.15) + postcss-normalize-display-values: 8.0.1(postcss@8.5.15) + postcss-normalize-positions: 8.0.1(postcss@8.5.15) + postcss-normalize-repeat-style: 8.0.1(postcss@8.5.15) + postcss-normalize-string: 8.0.1(postcss@8.5.15) + postcss-normalize-timing-functions: 8.0.1(postcss@8.5.15) + postcss-normalize-unicode: 8.0.1(postcss@8.5.15) + postcss-normalize-url: 8.0.1(postcss@8.5.15) + postcss-normalize-whitespace: 8.0.1(postcss@8.5.15) + postcss-ordered-values: 8.0.1(postcss@8.5.15) + postcss-reduce-initial: 8.0.1(postcss@8.5.15) + postcss-reduce-transforms: 8.0.1(postcss@8.5.15) + postcss-svgo: 8.0.1(postcss@8.5.15) + postcss-unique-selectors: 8.0.1(postcss@8.5.15) + cssnano-utils@5.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 + cssnano-utils@6.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + cssnano@7.1.9(postcss@8.5.15): dependencies: cssnano-preset-default: 7.0.17(postcss@8.5.15) lilconfig: 3.1.3 postcss: 8.5.15 + cssnano@8.0.2(postcss@8.5.15): + dependencies: + cssnano-preset-default: 8.0.2(postcss@8.5.15) + lilconfig: 3.1.3 + postcss: 8.5.15 + csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -12629,7 +13954,7 @@ snapshots: - supports-color - uploadthing - nitropack@2.13.4(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)): + nitropack@2.13.4(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.2) @@ -12682,7 +14007,7 @@ snapshots: pretty-bytes: 7.1.0 radix3: 1.1.2 rollup: 4.62.2 - rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2) + rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2) scule: 1.3.0 semver: 7.8.5 serve-placeholder: 2.0.2 @@ -12694,7 +14019,7 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.3.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)) + unimport: 6.3.0(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) unplugin-utils: 0.3.1 unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) untyped: 2.0.0 @@ -12906,31 +14231,28 @@ snapshots: - xml2js - yaml - nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0): + nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@dxup/nuxt': 0.4.1(magicast@0.5.3)(typescript@5.9.3) - '@nuxt/cli': 3.36.0(@nuxt/schema@3.21.7)(cac@6.7.14)(magicast@0.5.3) + '@nuxt/cli': 3.36.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.3) '@nuxt/devtools': 3.2.4(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) - '@nuxt/kit': 3.21.7(magicast@0.5.3) - '@nuxt/nitro-server': 3.21.7(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(typescript@5.9.3) - '@nuxt/schema': 3.21.7 - '@nuxt/telemetry': 2.8.0(@nuxt/kit@3.21.7(magicast@0.5.3)) - '@nuxt/vite-builder': 3.21.7(@types/node@24.7.2)(eslint@9.39.4(jiti@2.7.0))(magicast@0.5.3)(nuxt@3.21.7(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3))(yaml@2.9.0) + '@nuxt/kit': 4.4.8(magicast@0.5.3) + '@nuxt/nitro-server': 4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(db0@0.3.4)(ioredis@5.11.1)(magicast@0.5.3)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@parcel/watcher@2.5.6)(@types/node@24.7.2)(@vue/compiler-sfc@3.5.38)(cac@6.7.14)(db0@0.3.4)(eslint@9.39.4(jiti@2.7.0))(ioredis@5.11.1)(magicast@0.5.3)(optionator@0.9.4)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.62.2))(rollup@4.62.2)(terser@5.48.0)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(yaml@2.9.0))(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3) + '@nuxt/schema': 4.4.8 + '@nuxt/telemetry': 2.8.0(@nuxt/kit@4.4.8(magicast@0.5.3)) + '@nuxt/vite-builder': 4.4.8(5e770e72ca055b8ebf51d504a3c88ac6) '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3)) '@vue/shared': 3.5.38 - c12: 3.3.4(magicast@0.5.3) chokidar: 5.0.0 compatx: 0.2.0 consola: 3.4.2 - cookie-es: 2.0.1 + cookie-es: 3.1.1 defu: 6.1.7 - destr: 2.0.5 devalue: 5.8.1 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 - h3: 1.15.11 - hookable: 5.5.3 + hookable: 6.1.1 ignore: 7.0.5 impound: 1.1.5 jiti: 2.7.0 @@ -12943,12 +14265,13 @@ snapshots: ofetch: 1.5.1 ohash: 2.0.11 on-change: 6.0.2 - oxc-minify: 0.132.0 - oxc-parser: 0.132.0 - oxc-transform: 0.132.0 - oxc-walker: 1.0.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)) + oxc-minify: 0.133.0 + oxc-parser: 0.133.0 + oxc-transform: 0.133.0 + oxc-walker: 1.0.0(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) pathe: 2.0.3 perfect-debounce: 2.1.0 + picomatch: 4.0.4 pkg-types: 2.3.1 rou3: 0.8.1 scule: 1.3.0 @@ -12959,12 +14282,13 @@ snapshots: ultrahtml: 1.6.0 uncrypto: 0.1.3 unctx: 2.5.0 - unimport: 6.3.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)) + unhead: 2.1.15 + unimport: 6.3.0(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) unplugin: 3.0.0 - unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.38)(vue-router@4.6.4(vue@3.5.38(typescript@5.9.3)))(vue@3.5.38(typescript@5.9.3)) + unrouting: 0.1.7 untyped: 2.0.0 vue: 3.5.38(typescript@5.9.3) - vue-router: 4.6.4(vue@3.5.38(typescript@5.9.3)) + vue-router: 5.1.0(@vue/compiler-sfc@3.5.38)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)) optionalDependencies: '@parcel/watcher': 2.5.6 '@types/node': 24.7.2 @@ -12975,13 +14299,18 @@ snapshots: - '@azure/identity' - '@azure/keyvault-secrets' - '@azure/storage-blob' + - '@babel/plugin-proposal-decorators' + - '@babel/plugin-syntax-jsx' + - '@babel/plugin-syntax-typescript' - '@biomejs/biome' - '@capacitor/preferences' - '@deno/kv' - '@electric-sql/pglite' - '@libsql/client' - '@netlify/blobs' + - '@pinia/colada' - '@planetscale/database' + - '@rollup/plugin-babel' - '@upstash/redis' - '@vercel/blob' - '@vercel/functions' @@ -13008,6 +14337,7 @@ snapshots: - mysql2 - optionator - oxlint + - pinia - react-native-b4a - rolldown - rollup @@ -13026,8 +14356,6 @@ snapshots: - uploadthing - utf-8-validate - vite - - vls - - vti - vue-tsc - xml2js - yaml @@ -13147,6 +14475,29 @@ snapshots: '@oxc-minify/binding-win32-ia32-msvc': 0.132.0 '@oxc-minify/binding-win32-x64-msvc': 0.132.0 + oxc-minify@0.133.0: + optionalDependencies: + '@oxc-minify/binding-android-arm-eabi': 0.133.0 + '@oxc-minify/binding-android-arm64': 0.133.0 + '@oxc-minify/binding-darwin-arm64': 0.133.0 + '@oxc-minify/binding-darwin-x64': 0.133.0 + '@oxc-minify/binding-freebsd-x64': 0.133.0 + '@oxc-minify/binding-linux-arm-gnueabihf': 0.133.0 + '@oxc-minify/binding-linux-arm-musleabihf': 0.133.0 + '@oxc-minify/binding-linux-arm64-gnu': 0.133.0 + '@oxc-minify/binding-linux-arm64-musl': 0.133.0 + '@oxc-minify/binding-linux-ppc64-gnu': 0.133.0 + '@oxc-minify/binding-linux-riscv64-gnu': 0.133.0 + '@oxc-minify/binding-linux-riscv64-musl': 0.133.0 + '@oxc-minify/binding-linux-s390x-gnu': 0.133.0 + '@oxc-minify/binding-linux-x64-gnu': 0.133.0 + '@oxc-minify/binding-linux-x64-musl': 0.133.0 + '@oxc-minify/binding-openharmony-arm64': 0.133.0 + '@oxc-minify/binding-wasm32-wasi': 0.133.0 + '@oxc-minify/binding-win32-arm64-msvc': 0.133.0 + '@oxc-minify/binding-win32-ia32-msvc': 0.133.0 + '@oxc-minify/binding-win32-x64-msvc': 0.133.0 + oxc-parser@0.132.0: dependencies: '@oxc-project/types': 0.132.0 @@ -13172,6 +14523,31 @@ snapshots: '@oxc-parser/binding-win32-ia32-msvc': 0.132.0 '@oxc-parser/binding-win32-x64-msvc': 0.132.0 + oxc-parser@0.133.0: + dependencies: + '@oxc-project/types': 0.133.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.133.0 + '@oxc-parser/binding-android-arm64': 0.133.0 + '@oxc-parser/binding-darwin-arm64': 0.133.0 + '@oxc-parser/binding-darwin-x64': 0.133.0 + '@oxc-parser/binding-freebsd-x64': 0.133.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.133.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.133.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.133.0 + '@oxc-parser/binding-linux-arm64-musl': 0.133.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.133.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.133.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.133.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.133.0 + '@oxc-parser/binding-linux-x64-gnu': 0.133.0 + '@oxc-parser/binding-linux-x64-musl': 0.133.0 + '@oxc-parser/binding-openharmony-arm64': 0.133.0 + '@oxc-parser/binding-wasm32-wasi': 0.133.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.133.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.133.0 + '@oxc-parser/binding-win32-x64-msvc': 0.133.0 + oxc-transform@0.132.0: optionalDependencies: '@oxc-transform/binding-android-arm-eabi': 0.132.0 @@ -13195,6 +14571,29 @@ snapshots: '@oxc-transform/binding-win32-ia32-msvc': 0.132.0 '@oxc-transform/binding-win32-x64-msvc': 0.132.0 + oxc-transform@0.133.0: + optionalDependencies: + '@oxc-transform/binding-android-arm-eabi': 0.133.0 + '@oxc-transform/binding-android-arm64': 0.133.0 + '@oxc-transform/binding-darwin-arm64': 0.133.0 + '@oxc-transform/binding-darwin-x64': 0.133.0 + '@oxc-transform/binding-freebsd-x64': 0.133.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.133.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.133.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.133.0 + '@oxc-transform/binding-linux-arm64-musl': 0.133.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.133.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.133.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.133.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.133.0 + '@oxc-transform/binding-linux-x64-gnu': 0.133.0 + '@oxc-transform/binding-linux-x64-musl': 0.133.0 + '@oxc-transform/binding-openharmony-arm64': 0.133.0 + '@oxc-transform/binding-wasm32-wasi': 0.133.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.133.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.133.0 + '@oxc-transform/binding-win32-x64-msvc': 0.133.0 + oxc-walker@1.0.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)): dependencies: magic-regexp: 0.11.0 @@ -13202,12 +14601,12 @@ snapshots: oxc-parser: 0.132.0 rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - oxc-walker@1.0.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)): + oxc-walker@1.0.0(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)): dependencies: magic-regexp: 0.11.0 optionalDependencies: - oxc-parser: 0.132.0 - rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + oxc-parser: 0.133.0 + rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) p-limit@3.1.0: dependencies: @@ -13314,35 +14713,72 @@ snapshots: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-colormin@8.0.1(postcss@8.5.15): + dependencies: + '@colordx/core': 5.4.3 + browserslist: 4.28.2 + caniuse-api: 4.0.0 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-convert-values@7.0.12(postcss@8.5.15): dependencies: browserslist: 4.28.2 postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-convert-values@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-discard-comments@7.0.8(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-selector-parser: 7.1.4 + postcss-discard-comments@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + postcss-discard-duplicates@7.0.4(postcss@8.5.15): dependencies: postcss: 8.5.15 + postcss-discard-duplicates@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-discard-empty@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 + postcss-discard-empty@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-discard-overridden@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 + postcss-discard-overridden@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-merge-longhand@7.0.7(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 stylehacks: 7.0.11(postcss@8.5.15) + postcss-merge-longhand@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + stylehacks: 8.0.1(postcss@8.5.15) + postcss-merge-rules@7.0.11(postcss@8.5.15): dependencies: browserslist: 4.28.2 @@ -13351,11 +14787,24 @@ snapshots: postcss: 8.5.15 postcss-selector-parser: 7.1.4 + postcss-merge-rules@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-api: 4.0.0 + cssnano-utils: 6.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + postcss-minify-font-values@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-minify-font-values@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-minify-gradients@7.0.5(postcss@8.5.15): dependencies: '@colordx/core': 5.4.3 @@ -13363,6 +14812,13 @@ snapshots: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-minify-gradients@8.0.1(postcss@8.5.15): + dependencies: + '@colordx/core': 5.4.3 + cssnano-utils: 6.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-minify-params@7.0.9(postcss@8.5.15): dependencies: browserslist: 4.28.2 @@ -13370,6 +14826,13 @@ snapshots: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-minify-params@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + cssnano-utils: 6.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-minify-selectors@7.1.2(postcss@8.5.15): dependencies: browserslist: 4.28.2 @@ -13378,6 +14841,14 @@ snapshots: postcss: 8.5.15 postcss-selector-parser: 7.1.4 + postcss-minify-selectors@8.0.2(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-api: 4.0.0 + cssesc: 3.0.0 + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + postcss-nested@7.0.2(postcss@8.5.15): dependencies: postcss: 8.5.15 @@ -13387,64 +14858,126 @@ snapshots: dependencies: postcss: 8.5.15 + postcss-normalize-charset@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-normalize-display-values@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-display-values@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-positions@7.0.4(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-positions@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-repeat-style@7.0.4(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-repeat-style@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-string@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-string@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-timing-functions@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-timing-functions@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-unicode@7.0.9(postcss@8.5.15): dependencies: browserslist: 4.28.2 postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-unicode@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-url@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-url@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-normalize-whitespace@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-normalize-whitespace@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-ordered-values@7.0.4(postcss@8.5.15): dependencies: cssnano-utils: 5.0.3(postcss@8.5.15) postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-ordered-values@8.0.1(postcss@8.5.15): + dependencies: + cssnano-utils: 6.0.1(postcss@8.5.15) + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-reduce-initial@7.0.9(postcss@8.5.15): dependencies: browserslist: 4.28.2 caniuse-api: 3.0.0 postcss: 8.5.15 + postcss-reduce-initial@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + caniuse-api: 4.0.0 + postcss: 8.5.15 + postcss-reduce-transforms@7.0.3(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-value-parser: 4.2.0 + postcss-reduce-transforms@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + postcss-selector-parser@7.1.4: dependencies: cssesc: 3.0.0 @@ -13456,11 +14989,22 @@ snapshots: postcss-value-parser: 4.2.0 svgo: 4.0.1 + postcss-svgo@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-value-parser: 4.2.0 + svgo: 4.0.1 + postcss-unique-selectors@7.0.7(postcss@8.5.15): dependencies: postcss: 8.5.15 postcss-selector-parser: 7.1.4 + postcss-unique-selectors@8.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + postcss-value-parser@4.2.0: {} postcss@8.4.31: @@ -13738,16 +15282,6 @@ snapshots: rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) rollup: 4.62.2 - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1))(rollup@4.62.2): - dependencies: - open: 11.0.0 - picomatch: 4.0.4 - source-map: 0.7.6 - yargs: 18.0.0 - optionalDependencies: - rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) - rollup: 4.62.2 - rollup@4.62.2: dependencies: '@types/estree': 1.0.9 @@ -14188,6 +15722,12 @@ snapshots: postcss: 8.5.15 postcss-selector-parser: 7.1.4 + stylehacks@8.0.1(postcss@8.5.15): + dependencies: + browserslist: 4.28.2 + postcss: 8.5.15 + postcss-selector-parser: 7.1.4 + stylis@4.2.0: {} superjson@2.2.6: @@ -14478,7 +16018,7 @@ snapshots: oxc-parser: 0.132.0 rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - unimport@6.3.0(oxc-parser@0.132.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1)): + unimport@6.3.0(oxc-parser@0.133.0)(rolldown@1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)): dependencies: acorn: 8.17.0 escape-string-regexp: 5.0.0 @@ -14495,8 +16035,8 @@ snapshots: unplugin: 3.0.0 unplugin-utils: 0.3.1 optionalDependencies: - oxc-parser: 0.132.0 - rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.11.1) + oxc-parser: 0.133.0 + rolldown: 1.0.0-beta.45(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) unpipe@1.0.0: {} @@ -14548,6 +16088,11 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 + unrouting@0.1.7: + dependencies: + escape-string-regexp: 5.0.0 + ufo: 1.6.4 + unrs-resolver@1.12.2: dependencies: napi-postinstall: 0.3.4 @@ -14683,6 +16228,21 @@ snapshots: optionator: 0.9.4 typescript: 5.9.3 + vite-plugin-checker@0.14.4(eslint@9.39.4(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + dependencies: + '@babel/code-frame': 7.29.7 + chokidar: 5.0.0 + npm-run-path: 6.0.0 + picocolors: 1.1.1 + picomatch: 4.0.4 + proper-lockfile: 4.1.2 + tiny-invariant: 1.3.3 + vite: 7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + optionalDependencies: + eslint: 9.39.4(jiti@2.7.0) + optionator: 0.9.4 + typescript: 5.9.3 + vite-plugin-inspect@11.4.1(@nuxt/kit@3.21.7(magicast@0.5.3))(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: ansis: 4.3.1 @@ -14891,11 +16451,6 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.30(typescript@5.9.3) - vue-router@4.6.4(vue@3.5.38(typescript@5.9.3)): - dependencies: - '@vue/devtools-api': 6.6.4 - vue: 3.5.38(typescript@5.9.3) - vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)): dependencies: '@babel/generator': 8.0.0 @@ -14920,6 +16475,30 @@ snapshots: '@vue/compiler-sfc': 3.5.38 vite: 7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)): + dependencies: + '@babel/generator': 8.0.0 + '@vue-macros/common': 3.1.2(vue@3.5.38(typescript@5.9.3)) + '@vue/devtools-api': 8.1.3 + ast-walker-scope: 0.9.0 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.4 + scule: 1.3.0 + tinyglobby: 0.2.17 + unplugin: 3.0.0 + unplugin-utils: 0.3.1 + vue: 3.5.38(typescript@5.9.3) + yaml: 2.9.0 + optionalDependencies: + '@vue/compiler-sfc': 3.5.38 + vite: 7.3.5(@types/node@24.7.2)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.38)(esbuild@0.28.1)(vue@3.5.30(typescript@5.9.3)): dependencies: '@babel/parser': 7.29.7 diff --git a/samples/browser/quickstart/README.md b/samples/browser/quickstart/README.md index 44a37b5..9d8182e 100644 --- a/samples/browser/quickstart/README.md +++ b/samples/browser/quickstart/README.md @@ -6,7 +6,22 @@ A minimal Vite + vanilla JS app demonstrating sign-in and sign-out with the Thun - Node.js 18+ - A running ThunderID instance (default: `https://localhost:8090`) -- A configured application with an authorized redirect URI set to your app's origin (e.g. `http://localhost:5173`) +- A configured application with an authorized redirect URI set to your app's origin (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + ```bash + BROWSER_QUICKSTART_CLIENT_ID=BROWSER_QUICKSTART + BROWSER_QUICKSTART_REDIRECT_URIS=["http://localhost:5173"] + ``` +2. Import via the ThunderID Console (https://localhost:8090/console): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `browser-quickstart` application under the default organization unit. ## Getting started @@ -15,9 +30,9 @@ A minimal Vite + vanilla JS app demonstrating sign-in and sign-out with the Thun cp .env.example .env ``` -2. Edit `.env` with your ThunderID application credentials: +2. Edit `.env` with the credentials you set in `thunderid-config/thunderid.env`: ``` - VITE_THUNDERID_CLIENT_ID= + VITE_THUNDERID_CLIENT_ID=BROWSER_QUICKSTART VITE_THUNDERID_BASE_URL=https://localhost:8090 ``` diff --git a/samples/browser/quickstart/src/auth.js b/samples/browser/quickstart/src/auth.js index 698ae11..bad532b 100644 --- a/samples/browser/quickstart/src/auth.js +++ b/samples/browser/quickstart/src/auth.js @@ -1,12 +1,18 @@ import { ThunderIDBrowserClient } from '@thunderid/browser' +const REQUIRED_ENV_VARS = ['VITE_THUNDERID_CLIENT_ID', 'VITE_THUNDERID_BASE_URL'] + +export const missingEnvVars = REQUIRED_ENV_VARS.filter((key) => !import.meta.env[key]) + const auth = new ThunderIDBrowserClient() -await auth.initialize({ - clientId: import.meta.env.VITE_THUNDERID_CLIENT_ID, - baseUrl: import.meta.env.VITE_THUNDERID_BASE_URL, - afterSignInUrl: window.location.origin, - afterSignOutUrl: window.location.origin, -}) +if (missingEnvVars.length === 0) { + await auth.initialize({ + clientId: import.meta.env.VITE_THUNDERID_CLIENT_ID, + baseUrl: import.meta.env.VITE_THUNDERID_BASE_URL, + afterSignInUrl: window.location.origin, + afterSignOutUrl: window.location.origin, + }) +} export default auth diff --git a/samples/browser/quickstart/src/components/nav.js b/samples/browser/quickstart/src/components/nav.js index 0953522..ada2f04 100644 --- a/samples/browser/quickstart/src/components/nav.js +++ b/samples/browser/quickstart/src/components/nav.js @@ -1,15 +1,10 @@ -const NAV_MARK = ` - - - -` - const ICON_MOON = `` const ICON_SUN = `` const ICON_KEY = `` -const ICON_USER = `` +const ICON_USER = `` const ICON_CHEVRON_LEFT = `` const ICON_CHEVRON_DOWN = `` +const ICON_TECH = `JS` function escapeHtml(str) { if (str == null) return '' @@ -31,39 +26,50 @@ function getInitials(user) { return name.slice(0, 2).toUpperCase() } -export function renderSignedOutNav({ isDark }) { +function getAvatarUrl(user) { + return user?.profile || user?.profileUrl || user?.picture || user?.URL || null +} + +function renderAvatar(user, className) { + const avatarUrl = getAvatarUrl(user) + if (avatarUrl) { + return `` + } + return `${escapeHtml(getInitials(user))}` +} + +function getFullName(user) { + const given = user?.given_name || '' + const family = user?.family_name || '' + if (given && family) return `${given} ${family}` + return user?.name || user?.username || 'User' +} + +export function renderSignedOutNav({ isDark, hideSignIn = false }) { return ` ` } export function renderSignedInNav({ user, isDark, currentPage }) { - const initials = escapeHtml(getInitials(user)) - const name = escapeHtml(user?.given_name || user?.name || user?.username || 'User') + const name = escapeHtml(getFullName(user)) const email = escapeHtml(user?.email || '') return ` ` } -export function attachNavHandlers({ isDark, setIsDark, navigateTo, auth }) { +export function attachNavHandlers({ isDark, setIsDark, navigateTo, auth, openManageProfile }) { document.getElementById('dark-toggle')?.addEventListener('click', () => { const next = !isDark() setIsDark(next) document.documentElement.classList.toggle('dark', next) const dt = document.getElementById('dark-toggle') if (dt) dt.innerHTML = next ? ICON_SUN : ICON_MOON - document.querySelectorAll('#mark-top, #hero-mark-top').forEach(el => + document.querySelectorAll('#hero-mark-top').forEach(el => el.setAttribute('fill', next ? '#E8F4FF' : '#05213F') ) }) @@ -133,14 +142,14 @@ export function attachNavHandlers({ isDark, setIsDark, navigateTo, auth }) { }, { once: true }) } - document.getElementById('ud-profile')?.addEventListener('click', () => { + document.getElementById('ud-token-debug')?.addEventListener('click', () => { menu?.classList.remove('ud-menu--open') - navigateTo('profile') + navigateTo('token') }) - document.getElementById('ud-token-debug')?.addEventListener('click', () => { + document.getElementById('ud-manage-profile')?.addEventListener('click', () => { menu?.classList.remove('ud-menu--open') - navigateTo('token') + openManageProfile?.() }) document.getElementById('ud-sign-out')?.addEventListener('click', () => { @@ -150,13 +159,12 @@ export function attachNavHandlers({ isDark, setIsDark, navigateTo, auth }) { export function attachSignedOutNavHandlers({ auth }) { document.getElementById('sign-in-btn')?.addEventListener('click', () => auth.signIn()) - document.getElementById('get-started-btn')?.addEventListener('click', () => auth.signIn()) document.getElementById('dark-toggle')?.addEventListener('click', () => { const next = !document.documentElement.classList.contains('dark') document.documentElement.classList.toggle('dark', next) const dt = document.getElementById('dark-toggle') if (dt) dt.innerHTML = next ? ICON_SUN : ICON_MOON - document.querySelectorAll('#mark-top').forEach(el => + document.querySelectorAll('#hero-mark-top').forEach(el => el.setAttribute('fill', next ? '#E8F4FF' : '#05213F') ) }) diff --git a/samples/browser/quickstart/src/components/profileDialog.js b/samples/browser/quickstart/src/components/profileDialog.js new file mode 100644 index 0000000..1f0634b --- /dev/null +++ b/samples/browser/quickstart/src/components/profileDialog.js @@ -0,0 +1,112 @@ +import { updateMeProfile } from '@thunderid/browser' + +const ICON_CLOSE = `` + +function escapeHtml(str) { + if (str == null) return '' + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +function getAvatarUrl(user) { + return user?.profile || user?.profileUrl || user?.picture || user?.URL || null +} + +function getInitials(user) { + const given = user?.given_name || '' + const family = user?.family_name || '' + if (given && family) return (given[0] + family[0]).toUpperCase() + const name = user?.name || user?.username || user?.email || '?' + const parts = name.trim().split(/\s+/) + if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase() + return name.slice(0, 2).toUpperCase() +} + +export function renderProfileDialog(user) { + const avatarUrl = getAvatarUrl(user) + const avatarHtml = avatarUrl + ? `` + : escapeHtml(getInitials(user)) + const given = escapeHtml(user?.given_name || '') + const family = escapeHtml(user?.family_name || '') + const email = escapeHtml(user?.email || user?.username || '') + + return ` +
+ +
` +} + +export function attachProfileDialogHandlers({ user, auth, onSaved }) { + const overlay = document.getElementById('profile-dialog-overlay') + const closeDialog = () => overlay?.remove() + + document.getElementById('profile-dialog-close')?.addEventListener('click', closeDialog) + document.getElementById('profile-dialog-cancel')?.addEventListener('click', closeDialog) + overlay?.addEventListener('click', (e) => { + if (e.target === overlay) closeDialog() + }) + + const errorEl = document.getElementById('profile-dialog-error') + const saveBtn = document.getElementById('profile-dialog-save') + + saveBtn?.addEventListener('click', async () => { + const givenName = document.getElementById('profile-first-name')?.value.trim() || '' + const familyName = document.getElementById('profile-last-name')?.value.trim() || '' + + saveBtn.disabled = true + saveBtn.textContent = 'Saving...' + if (errorEl) { errorEl.hidden = true; errorEl.textContent = '' } + + try { + await updateMeProfile({ + baseUrl: import.meta.env.VITE_THUNDERID_BASE_URL, + payload: { name: { givenName, familyName } }, + fetcher: async (url, config) => { + const token = await auth.getAccessToken() + return fetch(url, { + ...config, + headers: { ...config.headers, Authorization: `Bearer ${token}` }, + }) + }, + }) + + onSaved?.({ ...user, given_name: givenName, family_name: familyName }) + closeDialog() + } catch (err) { + if (errorEl) { + errorEl.hidden = false + errorEl.textContent = err?.message || 'Failed to update profile. Please try again.' + } + saveBtn.disabled = false + saveBtn.textContent = 'Save' + } + }) +} diff --git a/samples/browser/quickstart/src/main.js b/samples/browser/quickstart/src/main.js index 0f2f643..fac06fd 100644 --- a/samples/browser/quickstart/src/main.js +++ b/samples/browser/quickstart/src/main.js @@ -1,8 +1,8 @@ import './style.css' -import auth from './auth.js' +import auth, { missingEnvVars } from './auth.js' import { renderSignedOutNav, renderSignedInNav, attachNavHandlers, attachSignedOutNavHandlers } from './components/nav.js' -import { renderSignedOut, renderHome, startCountdown, attachSignedOutHandlers } from './pages/home.js' -import { renderProfile } from './pages/profile.js' +import { renderProfileDialog, attachProfileDialogHandlers } from './components/profileDialog.js' +import { renderSignedOut, renderHome, renderConfigNeeded, startCountdown, attachSignedOutHandlers } from './pages/home.js' import { renderTokenDebug, attachTokenHandlers } from './pages/token.js' let isDark = false @@ -25,8 +25,6 @@ function renderSignedInPage() { let content if (currentPage === 'token') { content = renderTokenDebug({ rawToken }) - } else if (currentPage === 'profile') { - content = renderProfile({ user }) } else { content = renderHome({ user, idToken }) } @@ -38,6 +36,7 @@ function renderSignedInPage() { setIsDark: (v) => { isDark = v }, navigateTo, auth, + openManageProfile, }) if (currentPage === 'token') { @@ -47,10 +46,31 @@ function renderSignedInPage() { } } +function openManageProfile() { + const app = document.getElementById('app') + if (!app) return + + app.insertAdjacentHTML('beforeend', renderProfileDialog(user)) + attachProfileDialogHandlers({ + user, + auth, + onSaved: (updatedUser) => { + user = updatedUser + renderSignedInPage() + }, + }) +} + async function renderApp() { const app = document.getElementById('app') if (!app) return + if (missingEnvVars.length > 0) { + app.innerHTML = renderSignedOutNav({ isDark, hideSignIn: true }) + renderConfigNeeded(missingEnvVars) + attachSignedOutNavHandlers({ auth }) + return + } + if (timer) { clearInterval(timer); timer = null } await auth.signIn({ callOnlyOnRedirect: true }) diff --git a/samples/browser/quickstart/src/pages/home.js b/samples/browser/quickstart/src/pages/home.js index 22bb403..711c377 100644 --- a/samples/browser/quickstart/src/pages/home.js +++ b/samples/browser/quickstart/src/pages/home.js @@ -60,6 +60,18 @@ function getInitials(user) { return name.slice(0, 2).toUpperCase() } +function getAvatarUrl(user) { + return user?.profile || user?.profileUrl || user?.picture || user?.URL || null +} + +function renderAvatar(user, className) { + const avatarUrl = getAvatarUrl(user) + if (avatarUrl) { + return `
` + } + return `
${escapeHtml(getInitials(user))}
` +} + function greeting(name) { const h = new Date().getHours() const tod = h < 12 ? 'morning' : h < 17 ? 'afternoon' : 'evening' @@ -92,7 +104,7 @@ export function renderSignedOut() {
${HERO_MARK}
- v1.0 · Open source + Open source

Auth for Modern Apps and Agents

@@ -101,8 +113,7 @@ export function renderSignedOut() { Clone the Quickstart and ship auth before lunch.

- - +

@@ -115,7 +126,7 @@ export function renderSignedOut() { Integration time
- MIT + Apache 2.0 License
@@ -124,15 +135,39 @@ export function renderSignedOut() { } export function attachSignedOutHandlers({ auth }) { - document.getElementById('hero-get-started-btn')?.addEventListener('click', () => auth.signIn()) document.getElementById('hero-sign-in-btn')?.addEventListener('click', () => auth.signIn()) } +export function renderConfigNeeded(missing) { + return ` +
+
+
${HERO_MARK}
+
+ + Setup required + +
+

Configuration needed

+

+ This quickstart can't reach ThunderID yet. Set the following + environment variable(s), then restart the dev server. +

+
    + ${missing.map(key => `
  • ${escapeHtml(key)}
  • `).join('')} +
+

+ Copy .env.example to .env.local, fill in the + values from your ThunderID application, then run npm run dev again. +

+
+
` +} + export function renderHome({ user, idToken }) { const now = Math.floor(Date.now() / 1000) const givenName = user?.given_name || user?.name || user?.username || 'there' const email = user?.email || '' - const initials = escapeHtml(getInitials(user)) const authTime = idToken?.auth_time const exp = idToken?.exp @@ -143,7 +178,7 @@ export function renderHome({ user, idToken }) { return `
-
${initials}
+ ${renderAvatar(user, 'home-avatar-initials')}

${escapeHtml(greeting(givenName))}

diff --git a/samples/browser/quickstart/src/pages/profile.js b/samples/browser/quickstart/src/pages/profile.js deleted file mode 100644 index e2f1263..0000000 --- a/samples/browser/quickstart/src/pages/profile.js +++ /dev/null @@ -1,45 +0,0 @@ -function escapeHtml(str) { - if (str == null) return '' - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') -} - -function row(label, value) { - if (!value) return '' - return ` -
-
${label}
-
${escapeHtml(value)}
-
` -} - -export function renderProfile({ user }) { - const displayName = user?.name || [user?.given_name, user?.family_name].filter(Boolean).join(' ') || user?.username || '—' - const picture = user?.picture - - return ` -
-
- ${picture - ? `${escapeHtml(displayName)}` - : `
${escapeHtml(displayName.slice(0, 2).toUpperCase())}
`} -
-

${escapeHtml(displayName)}

- ${user?.email ? `

${escapeHtml(user.email)}

` : ''} -
-
- - -
- ${row('First name', user?.given_name)} - ${row('Last name', user?.family_name)} - ${row('Username', user?.username)} - ${row('Email', user?.email)} - ${row('User ID', user?.sub || user?.id)} -
-
` -} diff --git a/samples/browser/quickstart/src/style.css b/samples/browser/quickstart/src/style.css index 47d0e99..de707e9 100644 --- a/samples/browser/quickstart/src/style.css +++ b/samples/browser/quickstart/src/style.css @@ -51,15 +51,20 @@ body { .nav { display: flex; align-items: center; - justify-content: space-between; - padding: 0 24px; - height: 60px; + gap: 12px; + padding: 0 28px; + height: 52px; border-bottom: 1px solid var(--border); - background: var(--card); + background: rgba(247, 249, 252, 0.92); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); position: sticky; top: 0; z-index: 100; - box-shadow: var(--shadow-sm); +} + +.dark .nav { + background: rgba(8, 15, 28, 0.92); } .nav-logo { @@ -68,6 +73,8 @@ body { gap: 10px; text-decoration: none; color: inherit; + cursor: pointer; + flex: 1; } .wordmark { @@ -111,7 +118,7 @@ body { } .hero-mark { - margin-bottom: 28px; + margin-bottom: 32px; opacity: 0.9; } @@ -119,12 +126,12 @@ body { display: inline-flex; align-items: center; gap: 8px; - font-size: 10px; - font-weight: 600; + font-size: 11px; + font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); - margin-bottom: 24px; + margin-bottom: 28px; } .hero-badge-line { @@ -141,7 +148,7 @@ body { letter-spacing: -0.05em; line-height: 1.1; color: var(--text); - margin-bottom: 20px; + margin-bottom: 18px; } .hero-subtitle { @@ -155,141 +162,135 @@ body { .hero-ctas { display: flex; - gap: 12px; + gap: 10px; flex-wrap: wrap; justify-content: center; - margin-bottom: 48px; + margin-bottom: 52px; } .hero-divider { width: 100%; border: none; border-top: 1px solid var(--border); - margin-bottom: 36px; + margin: 4px 0; } .hero-stats { - display: flex; - gap: 0; width: 100%; max-width: 480px; - border: 1px solid var(--border); - border-radius: var(--radius); - overflow: hidden; - background: var(--card); - box-shadow: var(--shadow-sm); + display: grid; + grid-template-columns: repeat(3, 1fr); } .stat { - flex: 1; display: flex; flex-direction: column; align-items: center; - padding: 20px 12px; - border-right: 1px solid var(--border); + gap: 4px; + padding: 16px 8px; } -.stat:last-child { - border-right: none; +.stat:not(:last-child) { + border-right: 1px solid var(--border); } .stat-value { - font-size: 18px; + font-size: 15px; font-weight: 700; - color: var(--blue); - letter-spacing: -0.3px; - line-height: 1; - margin-bottom: 4px; + color: var(--text); } .stat-label { - font-size: 11px; + font-size: 12px; color: var(--muted); - text-transform: uppercase; - letter-spacing: 0.05em; - font-weight: 500; } -/* Buttons */ -.btn-primary { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 9px 18px; - font-size: 14px; - font-weight: 600; - color: #ffffff; - background: var(--blue); - border: 1px solid transparent; +/* Config notice */ +.config-badge { + color: #e88b3a; +} + +.config-list { + width: 100%; + max-width: 420px; + text-align: left; + list-style: none; + background: var(--card); + border: 1px solid var(--border); border-radius: var(--radius-sm); - cursor: pointer; - transition: background 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease; - text-decoration: none; - white-space: nowrap; + padding: 0 16px; + margin: 4px 0 28px; } -.btn-primary:hover { - background: var(--blue-hover); - box-shadow: 0 2px 8px rgba(54, 136, 255, 0.35); +.config-list-item { + padding: 10px 0; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; + color: var(--text); } -.btn-primary:active { - transform: translateY(1px); +.config-list-item:not(:last-child) { + border-bottom: 1px solid var(--border); } -.btn-primary.btn-lg { - padding: 12px 28px; - font-size: 15px; - border-radius: var(--radius); +.config-hint { + font-size: 14px; + color: var(--muted); + line-height: 1.7; + max-width: 420px; } -.btn-outline { +.config-hint code { + background: var(--blue-subtle); + border-radius: 4px; + padding: 2px 6px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; +} + +/* Buttons */ +.btn-primary, +.btn-outline, +.btn-ghost { display: inline-flex; align-items: center; justify-content: center; - gap: 6px; - padding: 9px 18px; - font-size: 14px; + height: 46px; + padding: 0 24px; + border-radius: 8px; + font-size: 15px; font-weight: 600; - color: var(--text); - background: transparent; - border: 1.5px solid rgba(54, 136, 255, 0.4); - border-radius: var(--radius-sm); cursor: pointer; - transition: border-color 0.15s ease, background 0.15s ease; - text-decoration: none; + transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease; white-space: nowrap; + text-decoration: none; + font-family: inherit; } -.btn-outline:hover { - border-color: var(--blue); +.btn-primary { + background: var(--blue); + color: #ffffff; + border: 1.5px solid var(--blue); +} + +.btn-primary:hover { + opacity: 0.88; +} + +.btn-outline { + background: transparent; color: var(--blue); - background: var(--blue-subtle); + border: 1.5px solid rgba(54, 136, 255, 0.4); } -.btn-outline.btn-lg { - padding: 12px 28px; - font-size: 15px; - border-radius: var(--radius); +.btn-outline:hover { + background: var(--blue-subtle); } .btn-ghost { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 7px 14px; - font-size: 14px; - font-weight: 500; - color: var(--muted); background: transparent; + color: var(--muted); border: none; - border-radius: var(--radius-sm); - cursor: pointer; - transition: background 0.15s ease, color 0.15s ease; - text-decoration: none; - white-space: nowrap; } .btn-ghost:hover { @@ -297,24 +298,33 @@ body { color: var(--text); } +.nav .btn-primary, +.nav .btn-outline, +.nav .btn-ghost { + height: 36px; + padding: 0 16px; + font-size: 14px; +} + .dark-toggle { display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 34px; + height: 34px; padding: 0; background: transparent; - border: none; - border-radius: var(--radius-sm); + border: 1px solid var(--border); + border-radius: 8px; color: var(--muted); cursor: pointer; - transition: color 0.15s ease, background 0.15s ease; + transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease; + flex-shrink: 0; } .dark-toggle:hover { color: var(--text); - background: var(--blue-subtle); + background: var(--border); } /* User card */ @@ -463,6 +473,20 @@ body { font-size: 11px; font-weight: 700; flex-shrink: 0; + overflow: hidden; +} + +.ud-avatar img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +} + +.ud-avatar--header { + width: 36px; + height: 36px; + font-size: 13px; } .ud-name { @@ -491,6 +515,9 @@ body { } .ud-header { + display: flex; + align-items: center; + gap: 10px; padding: 12px 14px; } @@ -535,14 +562,6 @@ body { background: var(--blue-subtle); } -.ud-item--signout { - color: #dc2626; -} - -.ud-item--signout:hover { - background: rgba(220, 38, 38, 0.06); -} - /* Signed-in home page */ .home-main { width: 100%; @@ -565,6 +584,14 @@ body { letter-spacing: -0.3px; flex-shrink: 0; box-shadow: 0 4px 12px rgba(54, 136, 255, 0.3); + overflow: hidden; +} + +.home-avatar-initials img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; } .home-greeting { @@ -749,102 +776,6 @@ body { padding-top: 2px; } -/* Profile page */ -.profile-main { - width: 100%; - max-width: 780px; - margin: 0 auto; - padding: 52px 28px 80px; -} - -.profile-header { - display: flex; - align-items: center; - gap: 20px; - margin-bottom: 36px; -} - -.profile-avatar-img { - width: 64px; - height: 64px; - border-radius: 50%; - object-fit: cover; -} - -.profile-avatar-initials { - width: 64px; - height: 64px; - border-radius: 50%; - background: rgba(54, 136, 255, 0.12); - color: var(--blue); - display: flex; - align-items: center; - justify-content: center; - font-size: 22px; - font-weight: 700; - flex-shrink: 0; -} - -.profile-name { - font-size: 22px; - font-weight: 700; - color: var(--text); - letter-spacing: -0.03em; - margin: 0 0 4px; -} - -.profile-email { - font-size: 13px; - color: var(--muted); - margin: 0; -} - -.profile-section-label { - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.09em; - color: var(--muted); - margin-bottom: 16px; -} - -.profile-fields { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: var(--radius); - overflow: hidden; -} - -.profile-row { - display: flex; - align-items: center; - gap: 16px; - padding: 14px 20px; - border-bottom: 1px solid var(--border); -} - -.profile-row:last-child { - border-bottom: none; -} - -.profile-label { - font-size: 12px; - font-weight: 600; - color: var(--muted); - text-transform: uppercase; - letter-spacing: 0.06em; - min-width: 110px; - flex-shrink: 0; -} - -.profile-value { - font-size: 13px; - color: var(--text); - font-family: var(--font-mono, monospace); - word-break: break-all; -} - /* Token debug page */ .token-main { width: 100%; @@ -1037,3 +968,133 @@ body { color: var(--text); letter-spacing: -0.01em; } + +/* Manage Profile dialog */ +.profile-dialog-overlay { + position: fixed; + inset: 0; + background: rgba(5, 33, 63, 0.45); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + z-index: 300; +} + +.profile-dialog { + width: 100%; + max-width: 380px; + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow-md); + padding: 24px; +} + +.profile-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.profile-dialog-header h2 { + font-size: 16px; + font-weight: 700; + color: var(--text); +} + +.profile-dialog-close { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border: none; + background: transparent; + color: var(--muted); + border-radius: var(--radius-sm); + cursor: pointer; +} + +.profile-dialog-close:hover { + background: var(--blue-subtle); + color: var(--blue); +} + +.profile-dialog-avatar { + width: 64px; + height: 64px; + margin: 0 auto 20px; + border-radius: 50%; + background: var(--blue); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + font-weight: 700; + overflow: hidden; +} + +.profile-dialog-avatar img { + width: 100%; + height: 100%; + border-radius: 50%; + object-fit: cover; +} + +.profile-dialog-error { + font-size: 12px; + color: #dc2626; + background: rgba(220, 38, 38, 0.08); + border-radius: var(--radius-sm); + padding: 8px 10px; + margin-bottom: 14px; +} + +.profile-dialog-field { + margin-bottom: 14px; +} + +.profile-dialog-field label { + display: block; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--muted); + margin-bottom: 6px; +} + +.profile-dialog-field input { + width: 100%; + padding: 9px 12px; + font-size: 14px; + font-family: inherit; + color: var(--text); + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-sm); +} + +.profile-dialog-field input:focus { + outline: none; + border-color: var(--blue); +} + +.profile-dialog-readonly { + padding: 9px 12px; + font-size: 14px; + color: var(--muted); + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-sm); +} + +.profile-dialog-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 20px; +} diff --git a/samples/browser/quickstart/thunderid-config/thunderid-config.yaml b/samples/browser/quickstart/thunderid-config/thunderid-config.yaml new file mode 100644 index 0000000..99377cc --- /dev/null +++ b/samples/browser/quickstart/thunderid-config/thunderid-config.yaml @@ -0,0 +1,128 @@ +# resource_type: user_type +id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f +category: user +name: Customer +ouHandle: default +allowSelfRegistration: true +systemAttributes: + display: username +schema: { + "username": { + "type": "string", + "displayName": "Username", + "required": true, + "unique": true + }, + "password": { + "type": "string", + "displayName": "Password", + "required": false, + "credential": true + }, + "email": { + "type": "string", + "displayName": "Email", + "required": true, + "unique": true, + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "given_name": { + "type": "string", + "displayName": "First Name", + "required": false + }, + "family_name": { + "type": "string", + "displayName": "Last Name", + "required": false + }, + "name": { + "type": "string", + "displayName": "Full Name", + "required": false + }, + "mobile_number": { + "type": "string", + "displayName": "Mobile Number", + "required": false + } + } + +--- +# resource_type: application +id: 2803b8eb-d167-4c69-bc7f-dad6b4d31555 +ouHandle: default +name: browser-quickstart +description: Sample Vite + vanilla JS application using the @thunderid/browser SDK +url: http://localhost:5173 +logoUrl: emoji:🌐 +authFlowHandle: default-basic-flow +isRegistrationFlowEnabled: true +isRecoveryFlowEnabled: false +assertion: + validityPeriod: 3600 +allowedUserTypes: + - Customer +inboundAuthConfig: + - type: oauth2 + config: + clientId: {{.BROWSER_QUICKSTART_CLIENT_ID}} + redirectUris: + {{- range .BROWSER_QUICKSTART_REDIRECT_URIS}} + - {{.}} + {{- end}} + grantTypes: + - authorization_code + responseTypes: + - code + tokenEndpointAuthMethod: none + pkceRequired: true + publicClient: true + requirePushedAuthorizationRequests: false + token: + accessToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + idToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + responseType: JWT + userInfo: + responseType: JSON + userAttributes: + - given_name + - family_name + - email + - groups + - name + scopeClaims: + email: + - email + - email_verified + group: + - groups + phone: + - phone_number + - phone_number_verified + profile: + - name + - given_name + - family_name + - picture + +--- +# resource_type: server_config +name: cors +value: + allowedOrigins: + - "http://localhost:5173" diff --git a/samples/browser/quickstart/thunderid-config/thunderid.env b/samples/browser/quickstart/thunderid-config/thunderid.env new file mode 100644 index 0000000..c2dfd7f --- /dev/null +++ b/samples/browser/quickstart/thunderid-config/thunderid.env @@ -0,0 +1,2 @@ +BROWSER_QUICKSTART_CLIENT_ID=BROWSER_QUICKSTART +BROWSER_QUICKSTART_REDIRECT_URIS=["http://localhost:5173"] diff --git a/samples/express/quickstart/README.md b/samples/express/quickstart/README.md index 4d47c29..a3cd6a1 100644 --- a/samples/express/quickstart/README.md +++ b/samples/express/quickstart/README.md @@ -1,12 +1,36 @@ # ThunderID Express Quickstart -A minimal Express.js app demonstrating sign-in, sign-out, and route protection with the ThunderID JavaScript SDK (`@thunderid/express`). +A minimal Express.js **API** protected by ThunderID access tokens, using the ThunderID JavaScript +SDK (`@thunderid/express`). Unlike the browser-focused quickstarts, this sample doesn't have a +sign-in button — it's a resource server. Open [http://localhost:3000](http://localhost:3000) and +you'll get API documentation (endpoints, curl examples, a downloadable Postman collection) instead +of a login screen. + +Protected routes validate the `Authorization: Bearer ` header against ThunderID's OIDC +`/oauth2/userinfo` endpoint — the same check any OAuth 2.0 resource server performs. A `/login` → +`/token` helper is included purely so you have a fast way to mint a demo access token to test with. ## Prerequisites - Node.js 18+ - A running ThunderID instance (default: `https://localhost:8090`) -- A configured application with `http://localhost:3000/login` added as an authorized redirect URI +- A configured application with `http://localhost:3000/login` added as an authorized redirect URI (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + ```bash + EXPRESS_QUICKSTART_CLIENT_ID=EXPRESS_QUICKSTART + EXPRESS_QUICKSTART_CLIENT_SECRET= + EXPRESS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/login"] + ``` +2. Import via the ThunderID Console (https://localhost:8090/console): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `express-quickstart` application under the default organization unit. ## Getting started @@ -15,10 +39,10 @@ A minimal Express.js app demonstrating sign-in, sign-out, and route protection w cp .env.example .env ``` -2. Edit `.env` with your ThunderID application credentials: +2. Edit `.env` with the credentials you set in `thunderid-config/thunderid.env`: ``` - THUNDERID_CLIENT_ID= - THUNDERID_CLIENT_SECRET= + THUNDERID_CLIENT_ID=EXPRESS_QUICKSTART + THUNDERID_CLIENT_SECRET= THUNDERID_BASE_URL=https://localhost:8090 ``` @@ -28,9 +52,25 @@ A minimal Express.js app demonstrating sign-in, sign-out, and route protection w pnpm start ``` -Open [http://localhost:3000](http://localhost:3000) in your browser. +Open [http://localhost:3000](http://localhost:3000) for the API documentation page. + +## Trying the API + +- `GET /api/public` — no auth required. +- `GET /api/protected` and `GET /api/me` — require `Authorization: Bearer `. + +To get a token to test with, open [http://localhost:3000/login](http://localhost:3000/login), +sign in once, and your access token (decoded claims included) will be shown at +[http://localhost:3000/token](http://localhost:3000/token). Then: + +```sh +curl http://localhost:3000/api/me \ + -H "Authorization: Bearer $ACCESS_TOKEN" +``` -> **Note:** Ensure `http://localhost:3000/login` is registered as an authorized redirect URI in your ThunderID application settings. +Prefer a GUI? Download the Postman collection from the API docs page (or directly at +`/postman-collection.json`), paste your token into the `accessToken` collection variable, and run +the requests from there. ## Learn more diff --git a/samples/express/quickstart/index.js b/samples/express/quickstart/index.js deleted file mode 100644 index 35dcc23..0000000 --- a/samples/express/quickstart/index.js +++ /dev/null @@ -1,69 +0,0 @@ -const express = require('express'); -const cookieParser = require('cookie-parser'); -const { thunderID, handleSignIn, handleSignOut, protect } = require('@thunderid/express'); - -const app = express(); -const port = 3000; - -app.use(cookieParser()); -app.use(express.json()); - -app.use( - thunderID({ - baseUrl: process.env.THUNDERID_BASE_URL || 'https://localhost:8090', - clientId: process.env.THUNDERID_CLIENT_ID, - clientSecret: process.env.THUNDERID_CLIENT_SECRET, - afterSignInUrl: 'http://localhost:3000/login', - afterSignOutUrl: 'http://localhost:3000/logout', - }), -); - -app.get('/', (_req, res) => { - res.send('Protected page | Profile | Sign in'); -}); - -app.get('/login', handleSignIn()); -app.get('/logout', handleSignOut()); - -app.get( - '/protected', - protect((res) => res.redirect('/login')), - (_req, res) => { - res.send('You are signed in. View profile | Sign out'); - }, -); - -app.get('/profile', protect((res) => res.redirect('/login')), async (req, res) => { - const user = await req.thunderIDAuth.getUserFromRequest(req); - const esc = (s) => s == null ? '' : String(s).replace(/&/g, '&').replace(//g, '>'); - const displayName = esc(user.name || [user.given_name, user.family_name].filter(Boolean).join(' ') || user.username); - const row = (label, val) => val ? `${label}${esc(val)}` : ''; - res.send(` -Profile - - -

${displayName}

-${user.email ? `` : ''} - -${row('First name', user.given_name)} -${row('Last name', user.family_name)} -${row('Username', user.username)} -${row('Email', user.email)} -${row('User ID', user.sub || user.id)} -
-

← Home · Sign out

-`); -}); - -app.get('/me', protect(), async (req, res) => { - const user = await req.thunderIDAuth.getUserFromRequest(req); - res.json(user); -}); - -app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`); -}); diff --git a/samples/express/quickstart/index.mjs b/samples/express/quickstart/index.mjs new file mode 100644 index 0000000..f0372e9 --- /dev/null +++ b/samples/express/quickstart/index.mjs @@ -0,0 +1,329 @@ +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import express from 'express'; +import cookieParser from 'cookie-parser'; +import {thunderID, handleSignIn, handleSignOut, protect, SESSION_COOKIE_NAME} from '@thunderid/express'; +import {verifyBearerToken} from './lib/auth.mjs'; +import {layout, esc, escAttr, COPY_ICON} from './lib/layout.mjs'; +import {thunderMark} from './lib/thunderMark.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const app = express(); +const port = 3000; +const baseUrl = process.env.THUNDERID_BASE_URL || 'https://localhost:8090'; + +const REQUIRED_ENV_VARS = ['THUNDERID_CLIENT_ID', 'THUNDERID_CLIENT_SECRET']; +const missingEnvVars = REQUIRED_ENV_VARS.filter((key) => !process.env[key]); + +app.use(cookieParser()); +app.use(express.json()); +app.use(express.static(path.join(__dirname, 'public'))); + +// The thunderID() middleware is only used here to power the `/login` and +// `/token` convenience routes, which exist so you have a fast way to obtain +// a real access token to try against the API below. The API routes +// themselves (`/api/*`) don't rely on cookies or sessions at all — see +// lib/auth.mjs. +if (missingEnvVars.length === 0) { + app.use( + thunderID({ + baseUrl, + clientId: process.env.THUNDERID_CLIENT_ID, + clientSecret: process.env.THUNDERID_CLIENT_SECRET, + // afterSignInUrl doubles as the OAuth2 redirect_uri sent to ThunderID, so + // it must match the redirect URI registered for this app (`/login`, + // where handleSignIn() below actually exchanges the code). Where the + // user lands once sign-in completes is controlled separately via + // onSignIn. + afterSignInUrl: 'http://localhost:3000/login', + // Same story as afterSignInUrl: this is the post_logout_redirect_uri + // sent to ThunderID, so it must match handleSignOut()'s mount path + // (`/logout`) for the `?state=sign_out_success` callback below to fire. + afterSignOutUrl: 'http://localhost:3000/logout', + onSignIn: (res) => res.redirect('/'), + onSignOut: (res) => res.redirect('/'), + }), + ); +} + +const requireBearer = verifyBearerToken(baseUrl); + +async function getSession(req) { + const client = req.thunderIDAuth; + const sessionId = req.cookies?.[SESSION_COOKIE_NAME]; + if (!client || !sessionId) return {signedIn: false, accessToken: null, user: null}; + const signedIn = (await client.isSignedIn(sessionId)) ?? false; + if (!signedIn) return {signedIn: false, accessToken: null, user: null}; + const [accessToken, user] = await Promise.all([client.getAccessToken(sessionId), client.getUser(sessionId)]); + return {signedIn, accessToken, user}; +} + +// ── Configuration notice ──────────────────────────────────────────────── + +function renderConfigNeeded() { + return layout({ + title: 'Configuration needed', + body: `
+
Setup required
+

Configuration needed

+

This quickstart can't reach ThunderID yet. Set the following environment + variable(s), then restart the server.

+
    + ${missingEnvVars.map((key) => `
  • ${esc(key)}
  • `).join('')} +
+

Copy .env.example to .env, fill in the values from + your ThunderID application, then run npm start again.

+
`, + }); +} + +app.use((req, res, next) => { + if (missingEnvVars.length > 0 && req.path !== '/styles.css') { + return res.status(503).send(renderConfigNeeded()); + } + next(); +}); + +// ── API documentation / landing page ──────────────────────────────────── + +function curlBlock(lines) { + return `
${esc(lines.join('\n'))}
`; +} + +/** Joins a multi-line, backslash-continued curl command into one pasteable line. */ +function toSingleLineCommand(lines) { + return lines + .map((line) => line.trim().replace(/\\$/, '').trim()) + .join(' ') + .replace(/\s+/g, ' ') + .trim(); +} + +function copyButton(text) { + return ``; +} + +function endpoint({method, path: routePath, summary, protectedRoute, curl, sample}) { + return ` +
+ + ${esc(method)} + ${esc(routePath)} + ${esc(summary)} + ${protectedRoute ? 'Protected' : 'Public'} + +
+
+ Request + ${copyButton(toSingleLineCommand(curl))} +
+ ${curlBlock(curl)} +
Sample response
+
${esc(sample)}
+
+
`; +} + +function bearerLine(accessToken) { + return accessToken ? `Bearer ${accessToken}` : 'Bearer $ACCESS_TOKEN'; +} + +app.get('/', async (req, res) => { + const {signedIn, accessToken, user} = await getSession(req); + + const hero = ` +
+
+
${thunderMark(40)}
+
Open source
+

Auth for Modern Apps and Agents

+

ThunderID gives you OAuth 2.0, PKCE, MFA, and JWT out of + the box. Clone the Quickstart and ship auth before lunch.

+
+
+
OAuth 2.0Authorization standard
+
< 5 minIntegration time
+
Apache 2.0License
+
+
+
`; + + const authCard = signedIn + ? `
+

You're signed in. Here's your access token — it's already substituted into the curl + examples below, so you can copy and run them as-is.

+
+ + ${copyButton(accessToken || '')} +
+ ${esc(accessToken)} +

See the full decoded token, expiry, and claims at /token.

+
` + : `
+ +
${curlBlock(['Authorization: Bearer '])}
+

Sign in above for a demo token, or obtain one via OAuth 2.0 in your own app.

+
`; + + const body = `${hero} +
+ + ${authCard} + + + ${endpoint({ + method: 'GET', + path: '/api/public', + summary: 'No authentication required.', + protectedRoute: false, + curl: ['curl http://localhost:3000/api/public'], + sample: JSON.stringify({message: 'This endpoint is public. No token needed.'}, null, 2), + })} + ${endpoint({ + method: 'GET', + path: '/api/protected', + summary: 'Returns a sample resource for the authenticated caller.', + protectedRoute: true, + curl: [ + 'curl http://localhost:3000/api/protected \\', + ` -H "Authorization: ${bearerLine(accessToken)}"`, + ], + sample: JSON.stringify( + {message: 'You are authenticated.', subject: ''}, + null, + 2, + ), + })} + ${endpoint({ + method: 'GET', + path: '/api/me', + summary: "Proxies ThunderID's userinfo claims for the token's owner.", + protectedRoute: true, + curl: ['curl http://localhost:3000/api/me \\', ` -H "Authorization: ${bearerLine(accessToken)}"`], + sample: JSON.stringify({sub: '...', email: 'jane@example.com', given_name: 'Jane'}, null, 2), + })} + + +
+

Prefer a GUI? Download the collection below — it ships with requests for every + endpoint above and a {{accessToken}} variable you can fill in from + /token.

+ +
+
+ `; + res.send(layout({title: 'API docs', signedIn, user, body})); +}); + +// ── Demo token helper (browser-based, cookie session) ─────────────────── + +app.get('/login', handleSignIn()); +app.get('/logout', handleSignOut()); + +app.get('/token', protect((res) => res.redirect('/login')), async (req, res) => { + const {accessToken, user} = await getSession(req); + + const parts = accessToken ? accessToken.split('.') : []; + const decodePart = (part) => { + try { + return JSON.parse(Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')); + } catch { + return null; + } + }; + const header = parts[0] ? decodePart(parts[0]) : null; + const payload = parts[1] ? decodePart(parts[1]) : null; + const issuer = payload?.iss; + const audience = Array.isArray(payload?.aud) ? payload.aud.join(', ') : payload?.aud; + const scopes = payload?.scope; + + const body = `
+
+
+

Token debug

+

Inspect your access token and decoded claims.

+
+
Valid
+
+ +
+
+ + ${copyButton(accessToken || '')} +
+ ${esc(parts[0])}.${esc(parts[1])}.${esc(parts[2])} +
+ +
+
+
JWT Header
+
${esc(JSON.stringify(header, null, 2))}
+
+
+
JWT Payload
+
${esc(JSON.stringify(payload, null, 2))}
+
+
+ + ${ + issuer || audience || scopes + ? `
+ ${issuer ? `
Issuer
${esc(issuer)}
` : ''} + ${audience ? `
Audience
${esc(audience)}
` : ''} + ${scopes ? `
Scopes
${esc(scopes)}
` : ''} +
` + : '' + } + + +
`; + res.send(layout({title: 'Token debug', signedIn: true, showBack: true, user, body})); +}); + +// ── API routes (bearer-token protected, no cookies involved) ──────────── + +app.get('/api/public', (_req, res) => { + res.json({message: 'This endpoint is public. No token needed.'}); +}); + +app.get('/api/protected', requireBearer, (req, res) => { + res.json({ + message: 'You are authenticated.', + subject: req.thunderIDUserInfo.sub ?? req.thunderIDUserInfo.id, + }); +}); + +app.get('/api/me', requireBearer, (req, res) => { + res.json(req.thunderIDUserInfo); +}); + +// ── Postman collection download ───────────────────────────────────────── + +app.get('/postman-collection.json', (_req, res) => { + res.download(path.join(__dirname, 'postman', 'ThunderID-Express-Quickstart.postman_collection.json')); +}); + +app.listen(port, () => { + console.log(`Server running on http://localhost:${port}`); +}); diff --git a/samples/express/quickstart/lib/auth.mjs b/samples/express/quickstart/lib/auth.mjs new file mode 100644 index 0000000..61be3f3 --- /dev/null +++ b/samples/express/quickstart/lib/auth.mjs @@ -0,0 +1,47 @@ +/** + * Bearer-token protection for the API routes in this quickstart. + * + * This is a resource-server style check: it does not depend on a browser + * session or cookie. Any client (curl, Postman, another service) can call + * a protected endpoint by sending a ThunderID-issued access token in the + * `Authorization: Bearer ` header. The token is validated by asking + * ThunderID's OIDC userinfo endpoint whether it's still valid — the same + * endpoint any OAuth 2.0 resource server would call. + */ +export function verifyBearerToken(baseUrl) { + return async function bearerAuth(req, res, next) { + const header = req.headers.authorization || ''; + const [scheme, token] = header.split(' '); + + if (scheme !== 'Bearer' || !token) { + res.set('WWW-Authenticate', 'Bearer realm="thunderid-express-quickstart"'); + return res.status(401).json({ + error: 'unauthorized', + message: 'Missing or malformed Authorization header. Expected: Authorization: Bearer ', + }); + } + + let response; + try { + response = await fetch(`${baseUrl}/oauth2/userinfo`, { + headers: {Authorization: `Bearer ${token}`}, + }); + } catch { + return res.status(502).json({ + error: 'bad_gateway', + message: 'Could not reach ThunderID to validate the access token.', + }); + } + + if (!response.ok) { + res.set('WWW-Authenticate', 'Bearer error="invalid_token"'); + return res.status(401).json({ + error: 'invalid_token', + message: 'The access token is invalid or expired. Get a fresh one at /token.', + }); + } + + req.thunderIDUserInfo = await response.json(); + next(); + }; +} diff --git a/samples/express/quickstart/lib/expressLogo.mjs b/samples/express/quickstart/lib/expressLogo.mjs new file mode 100644 index 0000000..733e4bc --- /dev/null +++ b/samples/express/quickstart/lib/expressLogo.mjs @@ -0,0 +1,10 @@ +export function expressLogo(size = 24) { + return ` + + + + + + +`; +} diff --git a/samples/express/quickstart/lib/layout.mjs b/samples/express/quickstart/lib/layout.mjs new file mode 100644 index 0000000..9b4f97d --- /dev/null +++ b/samples/express/quickstart/lib/layout.mjs @@ -0,0 +1,96 @@ +import {expressLogo} from './expressLogo.mjs'; + +export function esc(value) { + if (value === null || value === undefined) return ''; + return String(value) + .replace(/&/g, '&') + .replace(//g, '>'); +} + +/** Safe for embedding inside a double-quoted HTML attribute. */ +export function escAttr(value) { + return esc(value).replace(/"/g, '"'); +} + +const MOON_ICON = ``; +const SUN_ICON = ``; +const CHEVRON_LEFT_ICON = ``; +const KEY_ICON = ``; +const SIGN_OUT_ICON = ``; +export const COPY_ICON = ``; + +function userMenu(user) { + const displayName = user?.name || [user?.given_name, user?.family_name].filter(Boolean).join(' ') || user?.username || user?.email || 'Account'; + const initial = displayName.charAt(0).toUpperCase(); + + return `
+ + ${esc(initial)} + ${esc(displayName)} + +
+
+ ${esc(initial)} + ${esc(displayName)} +
+ ${KEY_ICON}Token debug + ${SIGN_OUT_ICON}Sign out +
+
`; +} + +function nav({signedIn, showBack, user}) { + const authAction = signedIn ? userMenu(user) : `Sign in`; + + return ``; +} + +/** + * Wraps page content in the shared ThunderID quickstart shell. Uses the same + * nav/hero/button classnames and design tokens as the React and Next.js + * quickstarts so the sample apps read as one family. + */ +export function layout({title, body, signedIn = false, showBack = false, user = null}) { + return ` + + + + +${esc(title)} · ThunderID Express Quickstart + + + + +
+ ${nav({signedIn, showBack, user})} + ${body} +
+ + +`; +} diff --git a/samples/express/quickstart/lib/thunderMark.mjs b/samples/express/quickstart/lib/thunderMark.mjs new file mode 100644 index 0000000..32c210d --- /dev/null +++ b/samples/express/quickstart/lib/thunderMark.mjs @@ -0,0 +1,8 @@ +export function thunderMark(height = 40) { + const width = Math.round((height / 257) * 207); + return ` + + + +`; +} diff --git a/samples/express/quickstart/package.json b/samples/express/quickstart/package.json index 7101013..ebd669e 100644 --- a/samples/express/quickstart/package.json +++ b/samples/express/quickstart/package.json @@ -2,9 +2,10 @@ "name": "@thunderid/express-quickstart", "private": true, "version": "0.0.0", + "type": "module", "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" + "start": "node --env-file=.env index.mjs", + "dev": "node --env-file=.env --watch index.mjs" }, "dependencies": { "@thunderid/express": "workspace:*", diff --git a/samples/express/quickstart/postman/ThunderID-Express-Quickstart.postman_collection.json b/samples/express/quickstart/postman/ThunderID-Express-Quickstart.postman_collection.json new file mode 100644 index 0000000..7e0b995 --- /dev/null +++ b/samples/express/quickstart/postman/ThunderID-Express-Quickstart.postman_collection.json @@ -0,0 +1,47 @@ +{ + "info": { + "name": "ThunderID Express Quickstart", + "description": "Sample requests for the ThunderID Express API quickstart. Public endpoints work as-is; protected endpoints need an `accessToken` collection variable — grab one from http://localhost:3000/token after signing in through the sample app, or run the OAuth 2.0 flow yourself against your ThunderID instance.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + {"key": "baseUrl", "value": "http://localhost:3000"}, + {"key": "thunderIdBaseUrl", "value": "https://localhost:8090"}, + {"key": "accessToken", "value": ""} + ], + "item": [ + { + "name": "Public endpoint", + "request": { + "method": "GET", + "header": [], + "url": {"raw": "{{baseUrl}}/api/public", "host": ["{{baseUrl}}"], "path": ["api", "public"]} + } + }, + { + "name": "Protected resource", + "request": { + "method": "GET", + "header": [{"key": "Authorization", "value": "Bearer {{accessToken}}"}], + "url": {"raw": "{{baseUrl}}/api/protected", "host": ["{{baseUrl}}"], "path": ["api", "protected"]} + } + }, + { + "name": "Current user (userinfo)", + "request": { + "method": "GET", + "header": [{"key": "Authorization", "value": "Bearer {{accessToken}}"}], + "url": {"raw": "{{baseUrl}}/api/me", "host": ["{{baseUrl}}"], "path": ["api", "me"]} + } + }, + { + "name": "ThunderID userinfo (direct)", + "request": { + "method": "GET", + "header": [{"key": "Authorization", "value": "Bearer {{accessToken}}"}], + "url": {"raw": "{{thunderIdBaseUrl}}/oauth2/userinfo", "host": ["{{thunderIdBaseUrl}}"], "path": ["oauth2", "userinfo"]}, + "description": "Calls ThunderID directly, bypassing the sample app, to show what /api/me is proxying." + } + } + ] +} diff --git a/samples/express/quickstart/public/styles.css b/samples/express/quickstart/public/styles.css new file mode 100644 index 0000000..5a3a5ca --- /dev/null +++ b/samples/express/quickstart/public/styles.css @@ -0,0 +1,894 @@ +/* ── Reset ── */ +*, +::before, +::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* ── Design tokens (shared with the ThunderID React / Next.js quickstarts) ── */ +:root { + --color-bg: #f7f9fc; + --color-card: #ffffff; + --color-border: #dde3ec; + --color-text: #05213f; + --color-text-muted: #5a7085; + --color-muted: #5a7085; + --color-primary: #3688ff; + --color-primary-hover: #1f72f0; + --color-nav-bg: rgba(247, 249, 252, 0.92); + --color-nav-border: #dde3ec; + --color-code-bg: #0b1120; + --nav-height: 52px; + --shadow-card: 0 1px 3px 0 rgba(5, 33, 63, 0.06), 0 4px 12px 0 rgba(5, 33, 63, 0.08); + --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace; + --radius-card: 12px; + --radius-btn: 8px; + --transition: 0.2s ease; +} + +.dark, +html.dark { + --color-bg: #080f1c; + --color-card: #0f1a2d; + --color-border: rgba(255, 255, 255, 0.09); + --color-text: #e0eaff; + --color-text-muted: rgba(224, 234, 255, 0.48); + --color-muted: rgba(224, 234, 255, 0.48); + --color-nav-bg: rgba(8, 15, 28, 0.92); +} + +html, +body { + height: 100%; +} + +body { + font-family: var(--font-sans); + background: var(--color-bg); + color: var(--color-text); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.5; +} + +a { + color: var(--color-primary); +} + +.app { + min-height: 100vh; + background: var(--color-bg); + color: var(--color-text); + transition: background var(--transition), color var(--transition); +} + +/* ── Nav ── */ +.nav { + position: fixed; + top: 0; + left: 0; + right: 0; + height: var(--nav-height); + display: flex; + align-items: center; + padding: 0 28px; + background: var(--color-nav-bg); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--color-nav-border); + z-index: 100; + gap: 12px; +} + +.nav-logo { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + text-decoration: none; + color: inherit; +} + +.wordmark-name { + font-size: 14px; + font-weight: 700; + letter-spacing: -0.03em; + line-height: 1.1; + color: var(--color-text); +} + +.nav-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.nav-back-btn { + display: inline-flex; + align-items: center; + gap: 4px; + height: 32px; + padding: 0 10px; + border-radius: 6px; + color: var(--color-muted); + font-size: 13px; + text-decoration: none; +} + +.nav-back-btn:hover { + background: var(--color-border); + color: var(--color-text); +} + +/* ── Dark-mode toggle ── */ +.dark-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 8px; + border: 1px solid var(--color-border); + background: transparent; + color: var(--color-muted); + cursor: pointer; + flex-shrink: 0; + transition: border-color var(--transition), color var(--transition), background var(--transition); +} + +.dark-toggle:hover { + background: var(--color-border); + color: var(--color-text); +} + +.dark-toggle .icon-sun { + display: none; +} + +.dark .dark-toggle .icon-moon { + display: none; +} + +.dark .dark-toggle .icon-sun { + display: flex; +} + +/* ── Buttons ── */ +button { + font-family: var(--font-sans); +} + +.btn-primary, +.btn-outline, +.btn-ghost { + display: inline-flex; + align-items: center; + justify-content: center; + height: 46px; + padding: 0 24px; + border-radius: var(--radius-btn); + font-size: 15px; + font-weight: 600; + cursor: pointer; + border: none; + transition: opacity var(--transition), background var(--transition), border-color var(--transition); + white-space: nowrap; + text-decoration: none; + font-family: inherit; +} + +.btn-primary { + background: var(--color-primary); + color: #fff; + border: 1.5px solid var(--color-primary); +} + +.btn-primary:hover { + opacity: 0.88; +} + +.btn-outline { + background: transparent; + color: var(--color-primary); + border: 1.5px solid rgba(54, 136, 255, 0.4); +} + +.btn-outline:hover { + background: rgba(54, 136, 255, 0.08); +} + +.btn-ghost { + background: transparent; + color: var(--color-muted); +} + +.btn-ghost:hover { + background: rgba(0, 0, 0, 0.04); +} + +.nav .btn-primary, +.nav .btn-outline, +.nav .btn-ghost { + height: 36px; + padding: 0 16px; + font-size: 14px; +} + +/* ── User menu (signed-in nav) ── */ +.user-menu { + position: relative; +} + +.user-menu-trigger { + display: flex; + align-items: center; + gap: 8px; + height: 36px; + padding: 0 12px 0 6px; + border-radius: 999px; + background: transparent; + border: none; + cursor: pointer; + list-style: none; + color: var(--color-text); + font-size: 13px; + font-weight: 600; +} + +.user-menu-trigger:hover { + background: var(--color-border); +} + +.user-menu-trigger::-webkit-details-marker { + display: none; +} + +.user-menu-trigger:focus, +.user-menu-trigger:focus-visible { + outline: none; +} + +.user-avatar-sm { + width: 26px; + height: 26px; + border-radius: 50%; + background: var(--color-primary); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + flex-shrink: 0; +} + +.user-avatar-lg { + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--color-primary); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; + flex-shrink: 0; +} + +.user-menu-list { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 220px; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + overflow: hidden; + z-index: 200; +} + +.user-menu-header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; +} + +.user-menu-header-name { + font-size: 14px; + font-weight: 700; + color: var(--color-text); + letter-spacing: -0.02em; +} + +.user-menu-item { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + font-size: 13.5px; + font-weight: 600; + color: var(--color-text); + text-decoration: none; + border-top: 1px solid var(--color-border); +} + +.user-menu-item:hover { + background: var(--color-bg); +} + +/* ── Hero ── */ +.hero { + margin-top: var(--nav-height); + padding: 88px 32px 56px; +} + +.hero-inner { + width: 100%; + max-width: 560px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.hero-mark { + display: flex; + justify-content: center; + margin-bottom: 32px; + color: var(--color-primary); +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--color-muted); + margin-bottom: 28px; +} + +.hero-badge-line { + display: block; + height: 1px; + width: 24px; + background: var(--color-border); + flex-shrink: 0; +} + +.hero-title { + font-size: 42px; + font-weight: 700; + letter-spacing: -0.05em; + line-height: 1.1; + color: var(--color-text); + margin: 0 0 18px; +} + +.hero-subtitle { + font-size: 16px; + color: var(--color-muted); + max-width: 440px; + line-height: 1.8; + letter-spacing: -0.01em; + margin: 0 auto 36px; +} + +.hero-divider { + width: 100%; + border: none; + border-top: 1px solid var(--color-border); + margin: 4px 0; +} + +.hero-stats { + width: 100%; + display: grid; + grid-template-columns: repeat(3, 1fr); +} + +.hero-stat { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 16px 8px; + position: relative; +} + +.hero-stat + .hero-stat::before { + content: ''; + position: absolute; + left: 0; + top: 20%; + height: 60%; + width: 1px; + background: var(--color-border); +} + +.hero-stat-value { + font-size: 15px; + font-weight: 700; + color: var(--color-text); +} + +.hero-stat-label { + font-size: 12px; + color: var(--color-muted); +} + +/* ── Content shell (non-hero pages / sections below the hero) ── */ +.page { + width: 100%; + max-width: 780px; + margin: 0 auto; + margin-top: var(--nav-height); + padding: 40px 28px 96px; +} + +.docs-shell { + width: 100%; + max-width: 780px; + margin: 0 auto; + padding: 0 28px 96px; +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--color-text-muted); + margin-bottom: 16px; +} + +.eyebrow-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: #2fbd6b; + flex-shrink: 0; +} + +.page-title { + font-size: 28px; + font-weight: 700; + letter-spacing: -0.04em; + line-height: 1.15; + margin-bottom: 12px; +} + +.page-subtitle { + font-size: 15px; + color: var(--color-text-muted); + max-width: 560px; + line-height: 1.7; + margin-bottom: 32px; +} + +.section-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--color-muted); + margin: 40px 0 14px; +} + +.section-label:first-of-type { + margin-top: 0; +} + +/* ── Cards ── */ +.card { + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 24px; +} + +.card + .card { + margin-top: 14px; +} + +.card p { + font-size: 13.5px; + color: var(--color-text-muted); + line-height: 1.7; +} + +.card p + p { + margin-top: 10px; +} + +.card p code { + background: rgba(54, 136, 255, 0.1); + border-radius: 4px; + padding: 2px 6px; + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--color-text); +} + +/* ── Endpoint list (swagger-like) ── */ +.endpoint { + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + background: var(--color-card); + overflow: hidden; +} + +.endpoint + .endpoint { + margin-top: 12px; +} + +.endpoint-summary { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + cursor: pointer; + list-style: none; +} + +.endpoint-summary::-webkit-details-marker { + display: none; +} + +.method-badge { + flex-shrink: 0; + font-family: var(--font-mono); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.03em; + padding: 4px 9px; + border-radius: 5px; + color: #fff; + background: #2fbd6b; +} + +.endpoint-path { + font-family: var(--font-mono); + font-size: 14px; + font-weight: 600; + color: var(--color-text); +} + +.endpoint-desc { + font-size: 13px; + color: var(--color-text-muted); + flex: 1; + min-width: 0; +} + +.tag { + flex-shrink: 0; + font-size: 10.5px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 3px 9px; + border-radius: 999px; +} + +.tag-public { + color: #2fbd6b; + background: rgba(47, 189, 107, 0.12); +} + +.tag-protected { + color: #e88b3a; + background: rgba(232, 139, 58, 0.12); +} + +.endpoint-body { + padding: 0 20px 20px; + border-top: 1px solid var(--color-border); +} + +.endpoint-body-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-muted); + margin: 16px 0 8px; +} + +/* ── Code blocks ── */ +.code-block { + position: relative; + background: var(--color-code-bg); + border-radius: 8px; + padding: 14px 16px; + overflow-x: auto; +} + +.code-block pre, +.code-block code { + font-family: var(--font-mono); + font-size: 12.5px; + line-height: 1.7; + color: #c9d1d9; + background: none; + padding: 0; + white-space: pre; +} + +/* ── Config notice ── */ +.config-list { + list-style: none; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-btn); + padding: 0 16px; + margin: 4px 0 24px; + width: 100%; + max-width: 420px; +} + +.config-list-item { + padding: 10px 0; + font-family: var(--font-mono); + font-size: 13px; +} + +.config-list-item:not(:last-child) { + border-bottom: 1px solid var(--color-border); +} + +/* ── Token debug page ── */ +.token-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 36px; + flex-wrap: wrap; +} + +.token-title { + margin: 0 0 5px; + font-size: 22px; + font-weight: 700; + letter-spacing: -0.04em; + color: var(--color-text); +} + +.token-subtitle { + margin: 0; + font-size: 13px; + color: var(--color-muted); + letter-spacing: -0.01em; +} + +.token-badge { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 700; + letter-spacing: -0.01em; + flex-shrink: 0; + background: rgba(47, 189, 107, 0.08); + border: 1px solid rgba(47, 189, 107, 0.22); + color: #2fbd6b; +} + +.token-badge--expired { + background: rgba(220, 38, 38, 0.08); + border: 1px solid rgba(220, 38, 38, 0.22); + color: #dc2626; +} + +.token-badge-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; + flex-shrink: 0; +} + +.token-raw-section { + margin-bottom: 20px; +} + +.token-raw-label-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.token-section-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--color-muted); +} + +.token-copy-btn { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + border-radius: 5px; + background: transparent; + border: 1px solid var(--color-border); + font-size: 11px; + font-weight: 600; + color: var(--color-muted); + cursor: pointer; + font-family: var(--font-sans); + letter-spacing: -0.01em; + transition: border-color 0.2s, color 0.2s; +} + +.token-copy-btn:hover { + border-color: var(--color-primary); + color: var(--color-primary); +} + +.token-raw { + display: block; + font-size: 11px; + font-family: var(--font-mono); + word-break: break-all; + line-height: 1.8; + color: #c9d1d9; + background: var(--color-code-bg); + padding: 14px 16px; + border-radius: 8px; + overflow-x: auto; +} + +.token-part--header { + color: #ff7b72; +} + +.token-part--payload { + color: #79c0ff; +} + +.token-part--signature { + color: #3fb950; +} + +.token-dot { + color: rgba(255, 255, 255, 0.2); +} + +.token-decoded-grid { + display: grid; + grid-template-columns: 1fr 1.7fr; + gap: 12px; + margin-bottom: 20px; +} + +.token-code-box { + background: var(--color-code-bg); + border-radius: 10px; + overflow: hidden; +} + +.token-code-box-header { + padding: 9px 14px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.09em; + text-transform: uppercase; +} + +.token-code-box-header--header { + color: #ff7b72; + border-bottom: 1px solid rgba(255, 123, 114, 0.15); +} + +.token-code-box-header--payload { + color: #79c0ff; + border-bottom: 1px solid rgba(121, 192, 255, 0.15); +} + +.token-code-pre { + margin: 0; + padding: 14px; + font-size: 11.5px; + line-height: 1.8; + font-family: var(--font-mono); + color: #c9d1d9; + overflow-x: auto; + max-height: 220px; +} + +.token-meta-row { + display: flex; + gap: 32px; + padding-top: 20px; + border-top: 1px solid var(--color-border); + flex-wrap: wrap; +} + +.token-meta-label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--color-muted); + margin-bottom: 5px; +} + +.token-meta-value { + font-size: 12px; + font-family: var(--font-mono); + color: var(--color-text); + letter-spacing: -0.01em; +} + +/* ── Profile table ── */ +.kv-table { + width: 100%; + border-collapse: collapse; +} + +.kv-table th, +.kv-table td { + padding: 12px 0; + text-align: left; + font-size: 13.5px; + border-bottom: 1px solid var(--color-border); +} + +.kv-table th { + width: 140px; + font-weight: 600; + color: var(--color-muted); + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.06em; +} + +.kv-table td { + font-family: var(--font-mono); + word-break: break-all; +} + +.footer-note { + margin-top: 48px; + padding-top: 24px; + border-top: 1px solid var(--color-border); + font-size: 12.5px; + color: var(--color-muted); + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +@media (max-width: 640px) { + .token-decoded-grid { + grid-template-columns: 1fr; + } + + .endpoint-summary { + flex-wrap: wrap; + } + + .hero-title { + font-size: 32px; + } +} diff --git a/samples/express/quickstart/thunderid-config/thunderid-config.yaml b/samples/express/quickstart/thunderid-config/thunderid-config.yaml new file mode 100644 index 0000000..a0c35c8 --- /dev/null +++ b/samples/express/quickstart/thunderid-config/thunderid-config.yaml @@ -0,0 +1,122 @@ +# resource_type: user_type +id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f +category: user +name: Customer +ouHandle: default +allowSelfRegistration: true +systemAttributes: + display: username +schema: { + "username": { + "type": "string", + "displayName": "Username", + "required": true, + "unique": true + }, + "password": { + "type": "string", + "displayName": "Password", + "required": false, + "credential": true + }, + "email": { + "type": "string", + "displayName": "Email", + "required": true, + "unique": true, + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "given_name": { + "type": "string", + "displayName": "First Name", + "required": false + }, + "family_name": { + "type": "string", + "displayName": "Last Name", + "required": false + }, + "name": { + "type": "string", + "displayName": "Full Name", + "required": false + }, + "mobile_number": { + "type": "string", + "displayName": "Mobile Number", + "required": false + } + } + +--- +# resource_type: application +id: eb2214bd-5889-49cb-b9a7-d063cee76fb8 +ouHandle: default +name: express-quickstart +description: Sample Express.js application using the @thunderid/express SDK +url: http://localhost:3000 +logoUrl: emoji:🚂 +authFlowHandle: default-basic-flow +isRegistrationFlowEnabled: true +isRecoveryFlowEnabled: false +assertion: + validityPeriod: 3600 +allowedUserTypes: + - Customer +inboundAuthConfig: + - type: oauth2 + config: + clientId: {{.EXPRESS_QUICKSTART_CLIENT_ID}} + clientSecret: {{.EXPRESS_QUICKSTART_CLIENT_SECRET}} + redirectUris: + {{- range .EXPRESS_QUICKSTART_REDIRECT_URIS}} + - {{.}} + {{- end}} + grantTypes: + - authorization_code + responseTypes: + - code + tokenEndpointAuthMethod: client_secret_basic + pkceRequired: false + publicClient: false + requirePushedAuthorizationRequests: false + token: + accessToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + idToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + responseType: JWT + userInfo: + responseType: JSON + userAttributes: + - given_name + - family_name + - email + - groups + - name + scopeClaims: + email: + - email + - email_verified + group: + - groups + phone: + - phone_number + - phone_number_verified + profile: + - name + - given_name + - family_name + - picture diff --git a/samples/express/quickstart/thunderid-config/thunderid.env b/samples/express/quickstart/thunderid-config/thunderid.env new file mode 100644 index 0000000..a8e3c92 --- /dev/null +++ b/samples/express/quickstart/thunderid-config/thunderid.env @@ -0,0 +1,3 @@ +EXPRESS_QUICKSTART_CLIENT_ID=EXPRESS_QUICKSTART +EXPRESS_QUICKSTART_CLIENT_SECRET=change-me-express-quickstart-secret +EXPRESS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/login"] diff --git a/samples/nextjs/quickstart/.env.example b/samples/nextjs/quickstart/.env.example index e36a6cb..d1bf3e3 100644 --- a/samples/nextjs/quickstart/.env.example +++ b/samples/nextjs/quickstart/.env.example @@ -1,6 +1,8 @@ NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 NEXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here NEXT_PUBLIC_THUNDERID_APPLICATION_ID=your-application-id-here +NEXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin +NEXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup THUNDERID_CLIENT_SECRET=your-client-secret-here THUNDERID_SECRET=generate-with-openssl-rand-base64-32 # DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production. diff --git a/samples/nextjs/quickstart/README.md b/samples/nextjs/quickstart/README.md index 2aef767..0e5df7d 100644 --- a/samples/nextjs/quickstart/README.md +++ b/samples/nextjs/quickstart/README.md @@ -6,7 +6,26 @@ A minimal Next.js 15 App Router application demonstrating ThunderID authenticati - Node.js 18+ - pnpm -- A ThunderID application (create one at [thunderid.dev](https://thunderid.dev)) +- A ThunderID application (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + + ```bash + NEXTJS_QUICKSTART_APPLICATION_ID= + NEXTJS_QUICKSTART_CLIENT_ID=NEXTJS_QUICKSTART + NEXTJS_QUICKSTART_CLIENT_SECRET= + NEXTJS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000"] + ``` + +2. Import via the ThunderID Console ([https://localhost:8090/console](https://localhost:8090/console)): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `nextjs-quickstart` application under the default organization unit. Note the `NEXTJS_QUICKSTART_APPLICATION_ID` value — unlike the client ID, this is a fixed id (not server-assigned) because it also drives the embedded sign-in/sign-up UI, so you'll copy it verbatim into `.env.local` below. ## Getting started @@ -16,22 +35,17 @@ A minimal Next.js 15 App Router application demonstrating ThunderID authenticati cp .env.example .env.local ``` -2. Fill in your ThunderID credentials in `.env.local`: +2. Fill in your ThunderID credentials in `.env.local`, using the values you set in `thunderid-config/thunderid.env`: ``` NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NEXT_PUBLIC_THUNDERID_CLIENT_ID= - THUNDERID_CLIENT_SECRET= + NEXT_PUBLIC_THUNDERID_CLIENT_ID=NEXTJS_QUICKSTART + NEXT_PUBLIC_THUNDERID_APPLICATION_ID= + THUNDERID_CLIENT_SECRET= THUNDERID_SECRET= ``` -3. In your ThunderID application settings, add the following as an authorized redirect URL: - - ``` - http://localhost:3000 - ``` - -4. Start the development server: +3. Start the development server: ```bash pnpm dev diff --git a/samples/nextjs/quickstart/app/components/ConfigNotice.tsx b/samples/nextjs/quickstart/app/components/ConfigNotice.tsx new file mode 100644 index 0000000..0c9d8bb --- /dev/null +++ b/samples/nextjs/quickstart/app/components/ConfigNotice.tsx @@ -0,0 +1,83 @@ +'use client' +import { useState } from 'react' +import ThunderMark from './ThunderMark' +import NextLogo from './icons/NextLogo' + +function MoonIcon() { + return ( + + + + ) +} + +function SunIcon() { + return ( + + + + + + + + ) +} + +export default function ConfigNotice({ missing }: { missing: string[] }) { + const [dark, setDark] = useState(false) + + const toggle = () => { + const next = !dark + setDark(next) + document.documentElement.classList.toggle('dark', next) + } + + return ( +
+ + +
+
+
+ +
+ +
+ + Setup required + +
+ +

Configuration needed

+ +

+ This quickstart can't reach ThunderID yet. Set the following + environment variable(s), then restart the dev server. +

+ +
    + {missing.map((key) => ( +
  • {key}
  • + ))} +
+ +

+ Copy .env.example to .env.local, fill in the + values from your ThunderID application, then run npm run dev again. +

+
+
+
+ ) +} diff --git a/samples/nextjs/quickstart/app/components/HeroCtas.tsx b/samples/nextjs/quickstart/app/components/HeroCtas.tsx index df5e65f..ea8e91b 100644 --- a/samples/nextjs/quickstart/app/components/HeroCtas.tsx +++ b/samples/nextjs/quickstart/app/components/HeroCtas.tsx @@ -1,19 +1,12 @@ 'use client' -import { SignInButton, SignUpButton } from '@thunderid/nextjs' +import { SignInButton } from '@thunderid/nextjs' export default function HeroCtas() { return (
- - {({ signUp, isLoading }) => ( - - )} - {({ signIn, isLoading }) => ( - )} diff --git a/samples/nextjs/quickstart/app/components/Nav.tsx b/samples/nextjs/quickstart/app/components/Nav.tsx index 9e0b1aa..a837587 100644 --- a/samples/nextjs/quickstart/app/components/Nav.tsx +++ b/samples/nextjs/quickstart/app/components/Nav.tsx @@ -1,9 +1,9 @@ 'use client' import { usePathname } from 'next/navigation' import Link from 'next/link' -import { SignedIn, SignedOut, SignInButton, SignUpButton, UserDropdown } from '@thunderid/nextjs' +import { SignedIn, SignedOut, SignInButton, UserDropdown } from '@thunderid/nextjs' import { useState } from 'react' -import ThunderMark from './ThunderMark' +import NextLogo from './icons/NextLogo' function MoonIcon() { return ( @@ -43,15 +43,6 @@ function KeyIcon() { ) } -function UserIcon() { - return ( - - ) -} - export default function Nav() { const [dark, setDark] = useState(false) const pathname = usePathname() @@ -64,10 +55,6 @@ export default function Nav() { } const menuItems = [ - { - label: Profile, - href: '/profile', - }, { label: Token debug, href: '/token', @@ -77,11 +64,8 @@ export default function Nav() { return ( diff --git a/samples/nextjs/quickstart/app/components/icons/NextLogo.tsx b/samples/nextjs/quickstart/app/components/icons/NextLogo.tsx new file mode 100644 index 0000000..b78a766 --- /dev/null +++ b/samples/nextjs/quickstart/app/components/icons/NextLogo.tsx @@ -0,0 +1,17 @@ +export default function NextLogo({ size = 16, fill = 'currentColor' }: { size?: number; fill?: string }) { + return ( + + ) +} diff --git a/samples/nextjs/quickstart/app/globals.css b/samples/nextjs/quickstart/app/globals.css index b5f71b8..62bbaf0 100644 --- a/samples/nextjs/quickstart/app/globals.css +++ b/samples/nextjs/quickstart/app/globals.css @@ -73,8 +73,10 @@ body { display: flex; align-items: center; justify-content: space-between; - padding: 0 24px; + padding: 0 28px; background: var(--color-nav-bg); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--color-nav-border); z-index: 100; transition: background 0.2s, border-color 0.2s; @@ -136,20 +138,21 @@ body { display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 34px; + height: 34px; padding: 0; background: transparent; - border: none; + border: 1px solid var(--color-border); border-radius: var(--radius-btn); color: var(--color-text-muted); cursor: pointer; - transition: color 0.15s, background 0.15s; + transition: color 0.15s, background 0.15s, border-color 0.15s; + flex-shrink: 0; } .dark-toggle:hover { color: var(--color-text); - background: rgba(54, 136, 255, 0.06); + background: var(--color-border); } /* ── Buttons ── */ @@ -233,21 +236,28 @@ button:focus-visible { flex-direction: column; align-items: center; text-align: center; - gap: 20px; + gap: 0; max-width: 560px; width: 100%; margin: 0 auto; } +.hero-mark { + display: flex; + justify-content: center; + margin-bottom: 32px; +} + .hero-badge { display: inline-flex; align-items: center; gap: 8px; - font-size: 10px; - font-weight: 600; + font-size: 11px; + font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-text-muted); + margin-bottom: 28px; } .hero-badge-line { @@ -264,6 +274,7 @@ button:focus-visible { letter-spacing: -0.05em; color: var(--color-text); line-height: 1.1; + margin: 0 0 18px; } .hero-subtitle { @@ -272,6 +283,7 @@ button:focus-visible { max-width: 420px; line-height: 1.8; letter-spacing: -0.01em; + margin: 0 auto 36px; } .hero-ctas { @@ -279,14 +291,14 @@ button:focus-visible { gap: 12px; flex-wrap: wrap; justify-content: center; - margin-top: 4px; + margin-bottom: 52px; } .hero-divider { width: 100%; border: none; border-top: 1px solid var(--color-border); - margin: 8px 0; + margin: 4px 0; } /* ── Stats row ── */ @@ -294,9 +306,6 @@ button:focus-visible { display: grid; grid-template-columns: repeat(3, 1fr); width: 100%; - border: 1px solid var(--color-border); - border-radius: var(--radius-card); - overflow: hidden; } .hero-stat { @@ -304,7 +313,7 @@ button:focus-visible { flex-direction: column; align-items: center; gap: 4px; - padding: 20px 16px; + padding: 16px 8px; position: relative; } @@ -330,6 +339,50 @@ button:focus-visible { color: var(--color-text-muted); } +/* ── Config notice ── */ +.config-badge { + color: #e88b3a; +} + +.config-list { + width: 100%; + max-width: 420px; + text-align: left; + list-style: none; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-btn); + padding: 0 16px; + margin: 4px 0 28px; +} + +.config-list-item { + padding: 10px 0; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; + color: var(--color-text); +} + +.config-list-item:not(:last-child) { + border-bottom: 1px solid var(--color-border); +} + +.config-hint { + font-size: 14px; + color: var(--color-text-muted); + line-height: 1.7; + max-width: 420px; + margin: 0 auto; +} + +.config-hint code { + background: rgba(54, 136, 255, 0.1); + border-radius: 4px; + padding: 2px 6px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; +} + /* ── User / welcome card ── */ .user-card { display: flex; @@ -428,17 +481,6 @@ button:focus-visible { } .home-avatar { - width: 52px; - height: 52px; - border-radius: 50%; - background: var(--color-primary); - color: #ffffff; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - font-weight: 700; - letter-spacing: 0.02em; flex-shrink: 0; } @@ -642,19 +684,12 @@ button:focus-visible { color: var(--color-text); } -/* ── Profile page ── */ -.profile-main { - width: 100%; - max-width: 780px; - margin: 0 auto; - padding: 52px 28px 80px; -} - /* ── Token debug page ── */ .token-main { width: 100%; max-width: 780px; margin: 0 auto; + margin-top: 52px; padding: 52px 28px 80px; } @@ -682,6 +717,63 @@ button:focus-visible { letter-spacing: -0.01em; } +/* ── Sign in / Sign up pages ── */ +.auth-main { + width: 100%; + min-height: calc(100vh - 52px); + margin-top: 52px; + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; +} + +.auth-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 40px 32px; + max-width: 420px; + width: 100%; +} + +.auth-title { + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--color-text); + text-align: center; +} + +.auth-subtitle { + margin: 0 0 16px; + font-size: 13px; + color: var(--color-text-muted); + text-align: center; +} + +.auth-switch { + margin-top: 20px; + font-size: 13px; + color: var(--color-text-muted); + text-align: center; +} + +.auth-switch a { + color: var(--color-primary); + font-weight: 600; + text-decoration: none; +} + +.auth-switch a:hover { + text-decoration: underline; +} + .token-badge { display: inline-flex; align-items: center; diff --git a/samples/nextjs/quickstart/app/layout.tsx b/samples/nextjs/quickstart/app/layout.tsx index 1504fde..77535c2 100644 --- a/samples/nextjs/quickstart/app/layout.tsx +++ b/samples/nextjs/quickstart/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next' import { ThunderIDProvider } from '@thunderid/nextjs/server' +import ConfigNotice from './components/ConfigNotice' import './globals.css' export const dynamic = 'force-dynamic' @@ -9,17 +10,30 @@ export const metadata: Metadata = { description: 'ThunderID authentication with Next.js', } +const REQUIRED_ENV_VARS = [ + 'NEXT_PUBLIC_THUNDERID_BASE_URL', + 'NEXT_PUBLIC_THUNDERID_CLIENT_ID', + 'THUNDERID_CLIENT_SECRET', + 'THUNDERID_SECRET', +] + export default function RootLayout({ children, }: { children: React.ReactNode }) { + const missingEnvVars = REQUIRED_ENV_VARS.filter((key) => !process.env[key]) + return ( - - {children} - + {missingEnvVars.length > 0 ? ( + + ) : ( + + {children} + + )} ) diff --git a/samples/nextjs/quickstart/app/page.tsx b/samples/nextjs/quickstart/app/page.tsx index fb75a61..4773bad 100644 --- a/samples/nextjs/quickstart/app/page.tsx +++ b/samples/nextjs/quickstart/app/page.tsx @@ -1,5 +1,5 @@ 'use client' -import { SignedIn, SignedOut, User, useThunderID } from '@thunderid/nextjs' +import { SignedIn, SignedOut, User, UserAvatar, useThunderID } from '@thunderid/nextjs' import { useState, useEffect } from 'react' import { fetchAccessToken } from './actions' import HeroCtas from './components/HeroCtas' @@ -127,19 +127,15 @@ function HomeContent() { return ( - {(user: { firstName?: string; lastName?: string; email?: string } | null) => { - const givenName = user?.firstName ?? user?.email?.split('@')[0] ?? 'there' + {(user: { givenName?: string; given_name?: string; familyName?: string; email?: string } | null) => { + const givenName = user?.givenName ?? user?.given_name ?? user?.email?.split('@')[0] ?? 'there' const email = user?.email const ouName = organizationHandle ?? 'Default' - const initials = user - ? (`${(user.firstName?.[0] ?? '').toUpperCase()}${(user.lastName?.[0] ?? '').toUpperCase()}` || - user.email?.[0]?.toUpperCase()) ?? '?' - : '' return (
- +

{greeting(givenName)}

@@ -219,11 +215,13 @@ export default function HomePage() {
- +
+ +
- v1.0 · Open source + Open source
@@ -248,7 +246,7 @@ export default function HomePage() { Integration time
- MIT + Apache 2.0 License
diff --git a/samples/nextjs/quickstart/app/profile/page.tsx b/samples/nextjs/quickstart/app/profile/page.tsx deleted file mode 100644 index e05d1a2..0000000 --- a/samples/nextjs/quickstart/app/profile/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client' -import { UserProfile } from '@thunderid/nextjs' -import Nav from '../components/Nav' - -export default function ProfilePage() { - return ( -
-
- ) -} diff --git a/samples/nextjs/quickstart/app/signin/page.tsx b/samples/nextjs/quickstart/app/signin/page.tsx new file mode 100644 index 0000000..26ab4ce --- /dev/null +++ b/samples/nextjs/quickstart/app/signin/page.tsx @@ -0,0 +1,30 @@ +'use client' +import Link from 'next/link' +import { useRouter } from 'next/navigation' +import { SignedIn, SignedOut, SignIn } from '@thunderid/nextjs' +import Nav from '../components/Nav' + +export default function SignInPage() { + const router = useRouter() + + return ( +
+
+ ) +} diff --git a/samples/nextjs/quickstart/app/signup/page.tsx b/samples/nextjs/quickstart/app/signup/page.tsx new file mode 100644 index 0000000..d185861 --- /dev/null +++ b/samples/nextjs/quickstart/app/signup/page.tsx @@ -0,0 +1,27 @@ +'use client' +import Link from 'next/link' +import { SignedIn, SignedOut, SignUp } from '@thunderid/nextjs' +import Nav from '../components/Nav' + +export default function SignUpPage() { + return ( +
+
+ ) +} diff --git a/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml b/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml new file mode 100644 index 0000000..ceeb038 --- /dev/null +++ b/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml @@ -0,0 +1,125 @@ +# resource_type: user_type +id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f +category: user +name: Customer +ouHandle: default +allowSelfRegistration: true +systemAttributes: + display: username +schema: { + "username": { + "type": "string", + "displayName": "Username", + "required": true, + "unique": true + }, + "password": { + "type": "string", + "displayName": "Password", + "required": false, + "credential": true + }, + "email": { + "type": "string", + "displayName": "Email", + "required": true, + "unique": true, + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "given_name": { + "type": "string", + "displayName": "First Name", + "required": false + }, + "family_name": { + "type": "string", + "displayName": "Last Name", + "required": false + }, + "name": { + "type": "string", + "displayName": "Full Name", + "required": false + }, + "mobile_number": { + "type": "string", + "displayName": "Mobile Number", + "required": false + } + } + +--- +# resource_type: application +# id is pinned (not left to auto-assignment) because the Next.js SDK's +# embedded sign-in flow needs a fixed applicationId matching +# NEXT_PUBLIC_THUNDERID_APPLICATION_ID. +id: {{.NEXTJS_QUICKSTART_APPLICATION_ID}} +ouHandle: default +name: nextjs-quickstart +description: Sample Next.js App Router application using the @thunderid/nextjs SDK +url: http://localhost:3000 +logoUrl: emoji:▲ +authFlowHandle: default-basic-flow +isRegistrationFlowEnabled: true +isRecoveryFlowEnabled: false +assertion: + validityPeriod: 3600 +allowedUserTypes: + - Customer +inboundAuthConfig: + - type: oauth2 + config: + clientId: {{.NEXTJS_QUICKSTART_CLIENT_ID}} + clientSecret: {{.NEXTJS_QUICKSTART_CLIENT_SECRET}} + redirectUris: + {{- range .NEXTJS_QUICKSTART_REDIRECT_URIS}} + - {{.}} + {{- end}} + grantTypes: + - authorization_code + responseTypes: + - code + tokenEndpointAuthMethod: client_secret_basic + pkceRequired: true + publicClient: false + requirePushedAuthorizationRequests: false + token: + accessToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + idToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + responseType: JWT + userInfo: + responseType: JSON + userAttributes: + - given_name + - family_name + - email + - groups + - name + scopeClaims: + email: + - email + - email_verified + group: + - groups + phone: + - phone_number + - phone_number_verified + profile: + - name + - given_name + - family_name + - picture diff --git a/samples/nextjs/quickstart/thunderid-config/thunderid.env b/samples/nextjs/quickstart/thunderid-config/thunderid.env new file mode 100644 index 0000000..ac46a54 --- /dev/null +++ b/samples/nextjs/quickstart/thunderid-config/thunderid.env @@ -0,0 +1,4 @@ +NEXTJS_QUICKSTART_APPLICATION_ID=d1aa147c-9b2a-4a75-b97e-43656234ea8e +NEXTJS_QUICKSTART_CLIENT_ID=NEXTJS_QUICKSTART +NEXTJS_QUICKSTART_CLIENT_SECRET=change-me-nextjs-quickstart-secret +NEXTJS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000"] diff --git a/samples/node/quickstart/README.md b/samples/node/quickstart/README.md index 7804d38..26c37b1 100644 --- a/samples/node/quickstart/README.md +++ b/samples/node/quickstart/README.md @@ -6,7 +6,23 @@ A minimal plain Node.js HTTP server demonstrating sign-in and sign-out with the - Node.js 18+ - A running ThunderID instance (default: `https://localhost:8090`) -- A configured application with `http://localhost:3000/callback` added as an authorized redirect URI +- A configured application with `http://localhost:3000/callback` added as an authorized redirect URI (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + ```bash + NODE_QUICKSTART_CLIENT_ID=NODE_QUICKSTART + NODE_QUICKSTART_CLIENT_SECRET=
+ NODE_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/callback"] + ``` +2. Import via the ThunderID Console (https://localhost:8090/console): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `nodejs-quickstart` application under the default organization unit. ## Getting started @@ -15,10 +31,10 @@ A minimal plain Node.js HTTP server demonstrating sign-in and sign-out with the cp .env.example .env ``` -2. Edit `.env` with your ThunderID application credentials: +2. Edit `.env` with the credentials you set in `thunderid-config/thunderid.env`: ``` - THUNDERID_CLIENT_ID= - THUNDERID_CLIENT_SECRET= + THUNDERID_CLIENT_ID=NODE_QUICKSTART + THUNDERID_CLIENT_SECRET= THUNDERID_BASE_URL=https://localhost:8090 ``` @@ -30,8 +46,6 @@ A minimal plain Node.js HTTP server demonstrating sign-in and sign-out with the Open [http://localhost:3000](http://localhost:3000) in your browser. -> **Note:** Ensure `http://localhost:3000/callback` is registered as an authorized redirect URI in your ThunderID application settings. - ## Learn more - [ThunderID Docs](https://thunderid.dev/docs) diff --git a/samples/node/quickstart/index.js b/samples/node/quickstart/index.js index 75aca67..e43fd1b 100644 --- a/samples/node/quickstart/index.js +++ b/samples/node/quickstart/index.js @@ -6,8 +6,27 @@ const { ThunderIDNodeClient } = require('@thunderid/node'); const PORT = 3000; const SESSION_COOKIE = 'tid_session'; +const REQUIRED_ENV_VARS = ['THUNDERID_CLIENT_ID', 'THUNDERID_CLIENT_SECRET']; +const missingEnvVars = REQUIRED_ENV_VARS.filter((key) => !process.env[key]); + const auth = new ThunderIDNodeClient(); +function renderConfigNeeded() { + return ` +Configuration needed + + +

Configuration needed

+

This quickstart can't reach ThunderID yet. Set the following environment variable(s), then restart the server:

+
    ${missingEnvVars.map((key) => `
  • ${key}
  • `).join('')}
+

Copy .env.example to .env, fill in the values from your ThunderID application, then run node index.js again.

+`; +} + function getSessionId(req) { const cookieHeader = req.headers.cookie ?? ''; for (const part of cookieHeader.split(';')) { @@ -18,19 +37,25 @@ function getSessionId(req) { } async function main() { - await auth.initialize({ - clientId: process.env.THUNDERID_CLIENT_ID, - clientSecret: process.env.THUNDERID_CLIENT_SECRET, - baseUrl: process.env.THUNDERID_BASE_URL || 'https://localhost:8090', - afterSignInUrl: 'http://localhost:3000/callback', - afterSignOutUrl: 'http://localhost:3000', - }); + if (missingEnvVars.length === 0) { + await auth.initialize({ + clientId: process.env.THUNDERID_CLIENT_ID, + clientSecret: process.env.THUNDERID_CLIENT_SECRET, + baseUrl: process.env.THUNDERID_BASE_URL || 'https://localhost:8090', + afterSignInUrl: 'http://localhost:3000/callback', + afterSignOutUrl: 'http://localhost:3000', + }); + } const server = http.createServer(async (req, res) => { const url = new URL(req.url, `http://localhost:${PORT}`); try { - if (url.pathname === '/') { + if (url.pathname === '/' && missingEnvVars.length > 0) { + res.writeHead(200, { 'Content-Type': 'text/html' }); + return res.end(renderConfigNeeded()); + + } else if (url.pathname === '/') { const sessionId = getSessionId(req); const signedIn = sessionId && (await auth.isSignedIn(sessionId)); res.writeHead(200, { 'Content-Type': 'text/html' }); diff --git a/samples/node/quickstart/thunderid-config/thunderid-config.yaml b/samples/node/quickstart/thunderid-config/thunderid-config.yaml new file mode 100644 index 0000000..622b516 --- /dev/null +++ b/samples/node/quickstart/thunderid-config/thunderid-config.yaml @@ -0,0 +1,122 @@ +# resource_type: user_type +id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f +category: user +name: Customer +ouHandle: default +allowSelfRegistration: true +systemAttributes: + display: username +schema: { + "username": { + "type": "string", + "displayName": "Username", + "required": true, + "unique": true + }, + "password": { + "type": "string", + "displayName": "Password", + "required": false, + "credential": true + }, + "email": { + "type": "string", + "displayName": "Email", + "required": true, + "unique": true, + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "given_name": { + "type": "string", + "displayName": "First Name", + "required": false + }, + "family_name": { + "type": "string", + "displayName": "Last Name", + "required": false + }, + "name": { + "type": "string", + "displayName": "Full Name", + "required": false + }, + "mobile_number": { + "type": "string", + "displayName": "Mobile Number", + "required": false + } + } + +--- +# resource_type: application +id: 26d42c19-5d39-48c0-b764-153210c3f1eb +ouHandle: default +name: nodejs-quickstart +description: Sample plain Node.js server using the @thunderid/node SDK +url: http://localhost:3000 +logoUrl: emoji:🟢 +authFlowHandle: default-basic-flow +isRegistrationFlowEnabled: true +isRecoveryFlowEnabled: false +assertion: + validityPeriod: 3600 +allowedUserTypes: + - Customer +inboundAuthConfig: + - type: oauth2 + config: + clientId: {{.NODE_QUICKSTART_CLIENT_ID}} + clientSecret: {{.NODE_QUICKSTART_CLIENT_SECRET}} + redirectUris: + {{- range .NODE_QUICKSTART_REDIRECT_URIS}} + - {{.}} + {{- end}} + grantTypes: + - authorization_code + responseTypes: + - code + tokenEndpointAuthMethod: client_secret_basic + pkceRequired: false + publicClient: false + requirePushedAuthorizationRequests: false + token: + accessToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + idToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + responseType: JWT + userInfo: + responseType: JSON + userAttributes: + - given_name + - family_name + - email + - groups + - name + scopeClaims: + email: + - email + - email_verified + group: + - groups + phone: + - phone_number + - phone_number_verified + profile: + - name + - given_name + - family_name + - picture diff --git a/samples/node/quickstart/thunderid-config/thunderid.env b/samples/node/quickstart/thunderid-config/thunderid.env new file mode 100644 index 0000000..01cccc9 --- /dev/null +++ b/samples/node/quickstart/thunderid-config/thunderid.env @@ -0,0 +1,3 @@ +NODE_QUICKSTART_CLIENT_ID=NODE_QUICKSTART +NODE_QUICKSTART_CLIENT_SECRET=change-me-node-quickstart-secret +NODE_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/callback"] diff --git a/samples/nuxt/quickstart/.env.example b/samples/nuxt/quickstart/.env.example index 5f12159..e8af895 100644 --- a/samples/nuxt/quickstart/.env.example +++ b/samples/nuxt/quickstart/.env.example @@ -1,5 +1,8 @@ NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 NUXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here +NUXT_PUBLIC_THUNDERID_APPLICATION_ID=your-application-id-here +NUXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin +NUXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup THUNDERID_CLIENT_SECRET=your-client-secret-here THUNDERID_SESSION_SECRET=generate-with-openssl-rand-base64-32 # DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production. diff --git a/samples/nuxt/quickstart/.env.local b/samples/nuxt/quickstart/.env.local deleted file mode 100644 index 92de869..0000000 --- a/samples/nuxt/quickstart/.env.local +++ /dev/null @@ -1,3 +0,0 @@ -# Prevents Nuxt telemetry consent prompts from hanging non-interactive build and lint processes -# (e.g. nuxt module-build, nuxt prepare running inside turbo/CI). -NUXT_TELEMETRY_DISABLED=1 diff --git a/samples/nuxt/quickstart/README.md b/samples/nuxt/quickstart/README.md index 27401a9..21ea341 100644 --- a/samples/nuxt/quickstart/README.md +++ b/samples/nuxt/quickstart/README.md @@ -6,7 +6,26 @@ A minimal Nuxt 3 application demonstrating ThunderID authentication with OAuth 2 - Node.js 18+ - pnpm -- A ThunderID application (create one at [thunderid.dev](https://thunderid.dev)) +- A ThunderID application (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + + ```bash + NUXT_QUICKSTART_APPLICATION_ID= + NUXT_QUICKSTART_CLIENT_ID=NUXT_QUICKSTART + NUXT_QUICKSTART_CLIENT_SECRET=
+ NUXT_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/api/auth/callback"] + ``` + +2. Import via the ThunderID Console ([https://localhost:8090/console](https://localhost:8090/console)): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `nuxt-quickstart` application under the default organization unit. Note the `NUXT_QUICKSTART_APPLICATION_ID` value — unlike the client ID, this is a fixed id (not server-assigned) because it also drives the embedded sign-in UI, so you'll copy it verbatim into `.env` below. ## Getting started @@ -16,22 +35,17 @@ A minimal Nuxt 3 application demonstrating ThunderID authentication with OAuth 2 cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`: +2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`: ``` NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NUXT_PUBLIC_THUNDERID_CLIENT_ID= - THUNDERID_CLIENT_SECRET= + NUXT_PUBLIC_THUNDERID_CLIENT_ID=NUXT_QUICKSTART + NUXT_PUBLIC_THUNDERID_APPLICATION_ID= + THUNDERID_CLIENT_SECRET= THUNDERID_SESSION_SECRET= ``` -3. In your ThunderID application settings, add the following as an authorized redirect URL: - - ``` - http://localhost:3000/api/auth/callback - ``` - -4. Start the development server: +3. Start the development server: ```bash pnpm dev diff --git a/samples/nuxt/quickstart/app.vue b/samples/nuxt/quickstart/app.vue deleted file mode 100644 index 9cd418f..0000000 --- a/samples/nuxt/quickstart/app.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/samples/nuxt/quickstart/app/app.vue b/samples/nuxt/quickstart/app/app.vue new file mode 100644 index 0000000..3561126 --- /dev/null +++ b/samples/nuxt/quickstart/app/app.vue @@ -0,0 +1,15 @@ + + + diff --git a/samples/nuxt/quickstart/assets/styles.css b/samples/nuxt/quickstart/app/assets/styles.css similarity index 86% rename from samples/nuxt/quickstart/assets/styles.css rename to samples/nuxt/quickstart/app/assets/styles.css index 88fd782..4715b9c 100644 --- a/samples/nuxt/quickstart/assets/styles.css +++ b/samples/nuxt/quickstart/app/assets/styles.css @@ -310,6 +310,50 @@ button:focus-visible { color: var(--color-text-muted); } +/* ── Config notice ── */ +.config-badge { + color: #e88b3a; +} + +.config-list { + width: 100%; + max-width: 420px; + text-align: left; + list-style: none; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-btn); + padding: 0 16px; + margin: 4px 0 28px; +} + +.config-list-item { + padding: 10px 0; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; + color: var(--color-text); +} + +.config-list-item:not(:last-child) { + border-bottom: 1px solid var(--color-border); +} + +.config-hint { + font-size: 14px; + color: var(--color-text-muted); + line-height: 1.7; + max-width: 420px; + margin: 0 auto; +} + +.config-hint code { + background: rgba(54, 136, 255, 0.1); + border-radius: 4px; + padding: 2px 6px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; +} + /* ── Responsive ── */ @media (max-width: 480px) { .nav { @@ -367,6 +411,25 @@ button:focus-visible { min-width: 0; } +.home-avatar { + width: 52px; + height: 52px; + border-radius: 50%; + flex-shrink: 0; + object-fit: cover; +} + +.home-avatar--fallback { + display: flex; + align-items: center; + justify-content: center; + background: rgba(54, 136, 255, 0.12); + color: var(--color-primary); + font-size: 18px; + font-weight: 700; + letter-spacing: -0.02em; +} + .home-greeting-name { margin: 0 0 6px; font-size: 26px; @@ -540,6 +603,53 @@ button:focus-visible { padding-top: 2px; } +/* ── Sign in / Sign up pages ── */ +.auth-main { + width: 100%; + min-height: calc(100vh - 52px); + margin-top: 52px; + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; +} + +.auth-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + box-shadow: var(--shadow-card); + padding: 40px 32px; + max-width: 420px; + width: 100%; +} + +.auth-title { + font-size: 1.5rem; + font-weight: 700; + letter-spacing: -0.02em; + color: var(--color-text); + text-align: center; +} + +.auth-subtitle { + margin: 0 0 16px; + font-size: 13px; + color: var(--color-text-muted); + text-align: center; +} + +.auth-switch { + margin-top: 20px; + font-size: 13px; + color: var(--color-text-muted); + text-align: center; +} + /* ── Profile page ── */ .profile-main { width: 100%; diff --git a/samples/nuxt/quickstart/components/AppNav.vue b/samples/nuxt/quickstart/app/components/AppNav.vue similarity index 50% rename from samples/nuxt/quickstart/components/AppNav.vue rename to samples/nuxt/quickstart/app/components/AppNav.vue index 3a4cc9d..f7e7e3f 100644 --- a/samples/nuxt/quickstart/components/AppNav.vue +++ b/samples/nuxt/quickstart/app/components/AppNav.vue @@ -1,15 +1,13 @@ + + diff --git a/samples/nuxt/quickstart/app/pages/index.vue b/samples/nuxt/quickstart/app/pages/index.vue new file mode 100644 index 0000000..9ac9edf --- /dev/null +++ b/samples/nuxt/quickstart/app/pages/index.vue @@ -0,0 +1,234 @@ + + + diff --git a/samples/nuxt/quickstart/app/pages/signin.vue b/samples/nuxt/quickstart/app/pages/signin.vue new file mode 100644 index 0000000..63f529f --- /dev/null +++ b/samples/nuxt/quickstart/app/pages/signin.vue @@ -0,0 +1,24 @@ + + + diff --git a/samples/nuxt/quickstart/app/pages/signup.vue b/samples/nuxt/quickstart/app/pages/signup.vue new file mode 100644 index 0000000..300153e --- /dev/null +++ b/samples/nuxt/quickstart/app/pages/signup.vue @@ -0,0 +1,24 @@ + + + diff --git a/samples/nuxt/quickstart/pages/token.vue b/samples/nuxt/quickstart/app/pages/token.vue similarity index 100% rename from samples/nuxt/quickstart/pages/token.vue rename to samples/nuxt/quickstart/app/pages/token.vue diff --git a/samples/nuxt/quickstart/nuxt.config.ts b/samples/nuxt/quickstart/nuxt.config.ts index fc1e782..c35d5a4 100644 --- a/samples/nuxt/quickstart/nuxt.config.ts +++ b/samples/nuxt/quickstart/nuxt.config.ts @@ -1,4 +1,7 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ + compatibilityDate: '2025-07-15', + devtools: { enabled: true }, modules: ['@thunderid/nuxt'], css: ['~/assets/styles.css'], }) diff --git a/samples/nuxt/quickstart/package.json b/samples/nuxt/quickstart/package.json index 4f28816..489eb99 100644 --- a/samples/nuxt/quickstart/package.json +++ b/samples/nuxt/quickstart/package.json @@ -3,14 +3,14 @@ "private": true, "version": "0.0.0", "scripts": { - "dev": "nuxt dev", + "dev": "TMPDIR=/tmp/nuxt-tmp/ nuxt dev", "build": "nuxt build", "generate": "nuxt generate", "preview": "nuxt preview" }, "dependencies": { "@thunderid/nuxt": "workspace:*", - "nuxt": "^3.16.2", + "nuxt": "^4.4.8", "vue": "^3.5.13" } } diff --git a/samples/nuxt/quickstart/pages/index.vue b/samples/nuxt/quickstart/pages/index.vue deleted file mode 100644 index 5f5b916..0000000 --- a/samples/nuxt/quickstart/pages/index.vue +++ /dev/null @@ -1,243 +0,0 @@ - - - diff --git a/samples/nuxt/quickstart/pages/profile.vue b/samples/nuxt/quickstart/pages/profile.vue deleted file mode 100644 index d1e53da..0000000 --- a/samples/nuxt/quickstart/pages/profile.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml b/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml new file mode 100644 index 0000000..cb878a7 --- /dev/null +++ b/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml @@ -0,0 +1,125 @@ +# resource_type: user_type +id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f +category: user +name: Customer +ouHandle: default +allowSelfRegistration: true +systemAttributes: + display: username +schema: { + "username": { + "type": "string", + "displayName": "Username", + "required": true, + "unique": true + }, + "password": { + "type": "string", + "displayName": "Password", + "required": false, + "credential": true + }, + "email": { + "type": "string", + "displayName": "Email", + "required": true, + "unique": true, + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" + }, + "given_name": { + "type": "string", + "displayName": "First Name", + "required": false + }, + "family_name": { + "type": "string", + "displayName": "Last Name", + "required": false + }, + "name": { + "type": "string", + "displayName": "Full Name", + "required": false + }, + "mobile_number": { + "type": "string", + "displayName": "Mobile Number", + "required": false + } + } + +--- +# resource_type: application +# id is pinned (not left to auto-assignment) because the Nuxt SDK's +# embedded sign-in flow needs a fixed applicationId matching +# NUXT_PUBLIC_THUNDERID_APPLICATION_ID. +id: {{.NUXT_QUICKSTART_APPLICATION_ID}} +ouHandle: default +name: nuxt-quickstart +description: Sample Nuxt 3 application using the @thunderid/vue SDK +url: http://localhost:3000 +logoUrl: emoji:💚 +authFlowHandle: default-basic-flow +isRegistrationFlowEnabled: true +isRecoveryFlowEnabled: false +assertion: + validityPeriod: 3600 +allowedUserTypes: + - Customer +inboundAuthConfig: + - type: oauth2 + config: + clientId: {{.NUXT_QUICKSTART_CLIENT_ID}} + clientSecret: {{.NUXT_QUICKSTART_CLIENT_SECRET}} + redirectUris: + {{- range .NUXT_QUICKSTART_REDIRECT_URIS}} + - {{.}} + {{- end}} + grantTypes: + - authorization_code + responseTypes: + - code + tokenEndpointAuthMethod: client_secret_basic + pkceRequired: true + publicClient: false + requirePushedAuthorizationRequests: false + token: + accessToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + idToken: + validityPeriod: 3600 + userAttributes: + - given_name + - family_name + - email + - groups + - name + responseType: JWT + userInfo: + responseType: JSON + userAttributes: + - given_name + - family_name + - email + - groups + - name + scopeClaims: + email: + - email + - email_verified + group: + - groups + phone: + - phone_number + - phone_number_verified + profile: + - name + - given_name + - family_name + - picture diff --git a/samples/nuxt/quickstart/thunderid-config/thunderid.env b/samples/nuxt/quickstart/thunderid-config/thunderid.env new file mode 100644 index 0000000..5332fc6 --- /dev/null +++ b/samples/nuxt/quickstart/thunderid-config/thunderid.env @@ -0,0 +1,4 @@ +NUXT_QUICKSTART_APPLICATION_ID=26ab2782-20dd-4d8d-ab65-123944cf3db7 +NUXT_QUICKSTART_CLIENT_ID=NUXT_QUICKSTART +NUXT_QUICKSTART_CLIENT_SECRET=change-me-nuxt-quickstart-secret +NUXT_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/api/auth/callback"] diff --git a/samples/react/quickstart/README.md b/samples/react/quickstart/README.md index 8ca1f03..b35a480 100644 --- a/samples/react/quickstart/README.md +++ b/samples/react/quickstart/README.md @@ -6,7 +6,22 @@ A minimal React + Vite application demonstrating ThunderID authentication with O - Node.js 18+ - pnpm -- A ThunderID application — register at [thunderid.dev](https://thunderid.dev) +- A ThunderID application (see [Import ThunderID Resources](#import-thunderid-resources) below) + +## Import ThunderID Resources + +This sample ships with a `thunderid-config/` directory containing a declarative YAML file that creates the required user type and application in one step. + +1. Open `thunderid-config/thunderid.env` and set your preferred values: + ```bash + REACT_QUICKSTART_CLIENT_ID=REACT_QUICKSTART + REACT_QUICKSTART_REDIRECT_URIS=["http://localhost:5173"] + ``` +2. Import via the ThunderID Console (https://localhost:8090/console): + - **First-time login**: a welcome screen appears with an **Open** button to upload the YAML file directly. + - **Later**: access the same welcome screen from the user profile menu in the top-right corner of the console. + +This creates the `Customer` user type and the `react-quickstart` application under the default organization unit. ## Setup @@ -15,9 +30,9 @@ A minimal React + Vite application demonstrating ThunderID authentication with O cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`: +2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`: ``` - VITE_THUNDERID_CLIENT_ID=your-client-id + VITE_THUNDERID_CLIENT_ID=REACT_QUICKSTART VITE_THUNDERID_BASE_URL=https://your-thunderid-instance ``` diff --git a/samples/react/quickstart/src/App.css b/samples/react/quickstart/src/App.css index f82a388..67bcc52 100644 --- a/samples/react/quickstart/src/App.css +++ b/samples/react/quickstart/src/App.css @@ -284,6 +284,50 @@ color: var(--color-muted); } +/* ─── Config notice ─────────────────────────────────────────────────── */ +.config-badge { + color: #e88b3a; +} + +.config-list { + width: 100%; + max-width: 420px; + text-align: left; + list-style: none; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-btn); + padding: 0 16px; + margin: 4px 0 28px; +} + +.config-list-item { + padding: 10px 0; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; + color: var(--color-text); +} + +.config-list-item:not(:last-child) { + border-bottom: 1px solid var(--color-border); +} + +.config-hint { + font-size: 14px; + color: var(--color-muted); + line-height: 1.7; + max-width: 420px; + margin: 0 auto; +} + +.config-hint code { + background: rgba(54, 136, 255, 0.08); + border-radius: 4px; + padding: 2px 6px; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; +} + /* ─── Signed-in home page ───────────────────────────────────────────── */ .home-main { width: 100%; @@ -501,17 +545,11 @@ } /* ─── Token debug page ──────────────────────────────────────────── */ -.profile-main { - width: 100%; - max-width: 780px; - margin: 0 auto; - padding: 52px 28px 80px; -} - .token-main { width: 100%; max-width: 780px; margin: 0 auto; + margin-top: var(--nav-height); padding: 52px 28px 80px; } diff --git a/samples/react/quickstart/src/App.jsx b/samples/react/quickstart/src/App.jsx index 0b3d9b4..a988940 100644 --- a/samples/react/quickstart/src/App.jsx +++ b/samples/react/quickstart/src/App.jsx @@ -2,7 +2,6 @@ import { createBrowserRouter, RouterProvider } from 'react-router' import { ProtectedRoute } from '@thunderid/react-router' import Nav from './components/Nav' import HomePage from './pages/HomePage' -import ProfilePage from './pages/ProfilePage' import TokenDebugPage from './pages/TokenDebugPage' import './App.css' @@ -11,7 +10,6 @@ const router = createBrowserRouter([ element: