diff --git a/.changeset/nextjs-missing-exports.md b/.changeset/nextjs-missing-exports.md new file mode 100644 index 000000000..b6344c34b --- /dev/null +++ b/.changeset/nextjs-missing-exports.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/nextjs': patch +--- + +Complete the package's entry points. `@asgardeo/nextjs` now exports the `Loading` and `OrganizationList` components, and re-exports the hooks of the React SDK (`useUser`, `useOrganization`, `useTranslation`, `useTheme`, `useBrandingContext`, `useBranding`, `useFlow`, `useI18n`, `useForm`) so applications do not need to depend on `@asgardeo/react` themselves. `@asgardeo/nextjs/server` exports the server actions (`clearSession`, `isSignedIn`, `getSessionPayload`, `getUser`, `getUserProfile`, `httpRequest`, `signOut`, `switchOrganization`, the organization actions and more) for Server Components and Route Handlers, and the `asgardeo()` helper gained `isSignedIn`, `getSession`, `getUser`, `getUserProfile`, `signOut` and `clearSession`. diff --git a/packages/nextjs/src/__tests__/exports.test.ts b/packages/nextjs/src/__tests__/exports.test.ts new file mode 100644 index 000000000..45126c571 --- /dev/null +++ b/packages/nextjs/src/__tests__/exports.test.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2026, 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. + */ + +import {describe, expect, it, vi} from 'vitest'; +import * as clientExports from '../client'; +import * as serverExports from '../server'; + +vi.mock('next/headers', () => ({cookies: vi.fn(), headers: vi.fn()})); +vi.mock('next/navigation', () => ({useRouter: vi.fn(), useSearchParams: vi.fn()})); + +describe('package entry points', () => { + it('exports every client component and the hooks of the React SDK', () => { + const expected: string[] = [ + 'CreateOrganization', + 'Loading', + 'Organization', + 'OrganizationList', + 'OrganizationProfile', + 'OrganizationSwitcher', + 'SignIn', + 'SignInButton', + 'SignOutButton', + 'SignUp', + 'SignUpButton', + 'SignedIn', + 'SignedOut', + 'User', + 'UserDropdown', + 'UserProfile', + 'useAsgardeo', + 'useBranding', + 'useBrandingContext', + 'useFlow', + 'useForm', + 'useI18n', + 'useOrganization', + 'useTheme', + 'useTranslation', + 'useUser', + ]; + + expected.forEach((name: string) => { + expect(typeof (clientExports as Record)[name], name).toMatch(/function|object/); + }); + }); + + it('exports the server helper, the provider and the server actions', () => { + const expected: string[] = [ + 'AsgardeoProvider', + 'asgardeo', + 'clearSession', + 'createOrganization', + 'getAccessToken', + 'getAllOrganizations', + 'getMyOrganizations', + 'getOrganization', + 'getSessionId', + 'getSessionPayload', + 'getUser', + 'getUserProfile', + 'httpRequest', + 'isSignedIn', + 'refreshToken', + 'signOut', + 'switchOrganization', + 'updateUserProfile', + ]; + + expected.forEach((name: string) => { + expect(typeof (serverExports as Record)[name], name).toBe('function'); + }); + }); +}); diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 1320a994a..7e37dece0 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -18,6 +18,29 @@ export {default as useAsgardeo} from './contexts/Asgardeo/useAsgardeo'; +// Hooks of the underlying React SDK, re-exported so applications do not have to depend on `@asgardeo/react` +// themselves (a second copy of that package would come with its own, unrelated, React contexts). +export { + useBranding, + useBrandingContext, + useFlow, + useForm, + useI18n, + useOrganization, + useTheme, + useTranslation, + useUser, +} from '@asgardeo/react'; + +export {default as Loading} from './components/control/Loading/Loading'; +export type {LoadingProps} from './components/control/Loading/Loading'; + +export {default as OrganizationList} from './components/presentation/OrganizationList/OrganizationList'; +export type { + OrganizationListConfig, + OrganizationListProps, +} from './components/presentation/OrganizationList/OrganizationList'; + export {default as Organization} from './components/presentation/Organization/Organization'; export {OrganizationProps} from './components/presentation/Organization/Organization'; diff --git a/packages/nextjs/src/server/asgardeo.ts b/packages/nextjs/src/server/asgardeo.ts index 348dca150..e8b176a06 100644 --- a/packages/nextjs/src/server/asgardeo.ts +++ b/packages/nextjs/src/server/asgardeo.ts @@ -16,16 +16,51 @@ * under the License. */ -import {TokenExchangeRequestConfig, TokenResponse} from '@asgardeo/node'; +import {TokenExchangeRequestConfig, TokenResponse, User, UserProfile} from '@asgardeo/node'; +import clearSessionAction from './actions/clearSession'; import getSessionIdAction from './actions/getSessionId'; +import getSessionPayloadAction from './actions/getSessionPayload'; +import isSignedInAction from './actions/isSignedIn'; +import signOutAction from './actions/signOutAction'; import AsgardeoNextClient from '../AsgardeoNextClient'; import {AsgardeoNextConfig} from '../models/config'; +import {SessionTokenPayload} from '../utils/SessionManager'; +/** + * Server-side helper for Server Components, Route Handlers and Server Actions. + * + * @example + * ```ts + * import {asgardeo} from '@asgardeo/nextjs/server'; + * + * export default async function Page() { + * const {isSignedIn, getUser} = await asgardeo(); + * + * if (!(await isSignedIn())) { + * redirect('/signin'); + * } + * + * const user = await getUser(); + * ... + * } + * ``` + */ const asgardeo = async (): Promise<{ + /** Deletes the session cookies without contacting the identity server. */ + clearSession: () => Promise; exchangeToken: (config: TokenExchangeRequestConfig, sessionId: string) => Promise; getAccessToken: (sessionId: string) => Promise; + /** The verified session cookie payload, or `undefined` when there is no valid session. */ + getSession: () => Promise; getSessionId: () => Promise; + /** The signed-in user (SCIM2 profile, falling back to the ID token claims). */ + getUser: (sessionId?: string) => Promise; + /** The signed-in user's profile with its schemas. */ + getUserProfile: (sessionId?: string) => Promise; + isSignedIn: (sessionId?: string) => Promise; reInitialize: (config: Partial) => Promise; + /** Signs the user out: clears the session cookies and resolves the identity server's logout URL. */ + signOut: () => Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}>; }> => { const getAccessToken = async (sessionId: string): Promise => { const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); @@ -34,6 +69,25 @@ const asgardeo = async (): Promise<{ const getSessionId = async (): Promise => getSessionIdAction(); + const getSession = async (): Promise => getSessionPayloadAction(); + + const isSignedIn = async (sessionId?: string): Promise => isSignedInAction(sessionId); + + const getUser = async (sessionId?: string): Promise => { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + return client.getUser(sessionId); + }; + + const getUserProfile = async (sessionId?: string): Promise => { + const client: AsgardeoNextClient = AsgardeoNextClient.getInstance(); + return client.getUserProfile(sessionId); + }; + + const signOut = async (): Promise<{data?: {afterSignOutUrl?: string}; error?: unknown; success: boolean}> => + signOutAction(); + + const clearSession = async (): Promise => clearSessionAction(); + const exchangeToken = async ( config: TokenExchangeRequestConfig, sessionId: string, @@ -48,10 +102,16 @@ const asgardeo = async (): Promise<{ }; return { + clearSession, exchangeToken, getAccessToken, + getSession, getSessionId, + getUser, + getUserProfile, + isSignedIn, reInitialize, + signOut, }; }; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 49f3ec784..731fcdc65 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -22,3 +22,24 @@ export {default as asgardeo} from './asgardeo'; export {default as AsgardeoProvider} from './AsgardeoProvider.js'; export * from './AsgardeoProvider.js'; + +// Server actions, for use in Server Components, Route Handlers and other Server Actions. +export {default as clearSession} from './actions/clearSession'; +export {default as createOrganization} from './actions/createOrganization'; +export {default as getAccessToken} from './actions/getAccessToken'; +export {default as getAllOrganizations} from './actions/getAllOrganizations'; +export {default as getMyOrganizations} from './actions/getMyOrganizations'; +export {default as getOrganization} from './actions/getOrganizationAction'; +export {default as getSessionId} from './actions/getSessionId'; +export {default as getSessionPayload} from './actions/getSessionPayload'; +export {default as getUser} from './actions/getUserAction'; +export {default as getUserProfile} from './actions/getUserProfileAction'; +export {default as httpRequest} from './actions/httpRequestAction'; +export type {HttpRequestActionResult} from './actions/httpRequestAction'; +export {default as isSignedIn} from './actions/isSignedIn'; +export {default as refreshToken} from './actions/refreshToken'; +export type {RefreshResult} from './actions/refreshToken'; +export {default as signOut} from './actions/signOutAction'; +export {default as switchOrganization} from './actions/switchOrganization'; +export {default as updateUserProfile} from './actions/updateUserProfileAction'; +export type {SessionTokenPayload} from '../utils/SessionManager';