From a6bcbb72e402015498dfac9c9a74599d875b7582 Mon Sep 17 00:00:00 2001 From: DonOmalVindula Date: Sun, 6 Sep 2026 13:40:39 +0530 Subject: [PATCH] feat(nextjs): export the remaining components, the React SDK hooks and the server actions - The client entry now exports Loading and OrganizationList, and re-exports the React SDK hooks (useUser, useOrganization, useTranslation, useTheme, useBrandingContext, useBranding, useFlow, useI18n, useForm) so apps do not need their own dependency on @asgardeo/react, which would bring a second copy of the contexts. - The server entry exports the server actions (clearSession, isSignedIn, getSessionPayload, getUser, getUserProfile, httpRequest, signOut, switchOrganization, the organization actions, ...) as the JSDoc examples already assumed, and the asgardeo() helper gained isSignedIn, getSession, getUser, getUserProfile, signOut and clearSession. Co-Authored-By: Claude Fable 5.1 --- .changeset/nextjs-missing-exports.md | 5 ++ packages/nextjs/src/__tests__/exports.test.ts | 88 +++++++++++++++++++ packages/nextjs/src/client/index.ts | 23 +++++ packages/nextjs/src/server/asgardeo.ts | 62 ++++++++++++- packages/nextjs/src/server/index.ts | 21 +++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 .changeset/nextjs-missing-exports.md create mode 100644 packages/nextjs/src/__tests__/exports.test.ts 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';