From 4804b5138da8830a56d0c2a3796d092307493be4 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 13:44:45 +0530 Subject: [PATCH 1/6] chore(react-router): simplify ProtectedRoute component and remove unused authentication utilities --- packages/react-router/README.md | 232 ++++-------------- packages/react-router/package.json | 7 +- .../src/components/ProtectedRoute.tsx | 55 ++--- .../src/components/withAuthentication.tsx | 163 ------------ .../react-router/src/hooks/useAuthGuard.ts | 218 ---------------- packages/react-router/src/index.ts | 8 - 6 files changed, 63 insertions(+), 620 deletions(-) delete mode 100644 packages/react-router/src/components/withAuthentication.tsx delete mode 100644 packages/react-router/src/hooks/useAuthGuard.ts diff --git a/packages/react-router/README.md b/packages/react-router/README.md index b72a7625..3e940ea0 100644 --- a/packages/react-router/README.md +++ b/packages/react-router/README.md @@ -1,22 +1,21 @@ # @asgardeo/react-router -React Router integration for Asgardeo React SDK with protected routes and authentication guards. +React Router integration for Asgardeo React SDK with protected routes. [![npm version](https://img.shields.io/npm/v/@asgardeo/react-router.svg)](https://www.npmjs.com/package/@asgardeo/react-router) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ## Overview -`@asgardeo/react-router` is a supplementary package that provides seamless integration between Asgardeo authentication and React Router. It offers components and hooks to easily protect routes and handle authentication flows in your React applications. +`@asgardeo/react-router` is a supplementary package that provides seamless integration between Asgardeo authentication and React Router. It offers components to easily protect routes and handle authentication flows in your React applications. ## Features - 🛡️ **ProtectedRoute Component**: Drop-in replacement for React Router's Route with built-in authentication -- 🔒 **withAuthentication HOC**: Higher-order component for protecting any React component -- 🪝 **Authentication Hooks**: Powerful hooks for custom authentication logic -- 🔄 **Return URL Handling**: Automatic redirect back to intended destination after sign-in - ⚡ **TypeScript Support**: Full TypeScript support with comprehensive type definitions - 🎨 **Customizable**: Flexible configuration options for different use cases +- 🔒 **Authentication Guards**: Built-in authentication checking with loading states +- 🚀 **Lightweight**: Minimal bundle size with essential features only ## Installation @@ -33,7 +32,7 @@ pnpm add @asgardeo/react-router This package requires the following peer dependencies: ```bash -npm install @asgardeo/react react react-router-dom +npm install @asgardeo/react react react-router ``` ## Quick Start @@ -84,7 +83,7 @@ function App() { export default App; ``` -### 2. Custom Fallback and Redirects +### 2. Custom Fallback and Loading States ```tsx import { ProtectedRoute } from '@asgardeo/react-router'; @@ -120,7 +119,7 @@ import { ProtectedRoute } from '@asgardeo/react-router'; element={ Loading...} + loader={
Loading...
} >
@@ -128,182 +127,7 @@ import { ProtectedRoute } from '@asgardeo/react-router'; /> ``` -## API Reference - -### Components - -#### ProtectedRoute - -A component that protects routes based on authentication status. Should be used as the element prop of a Route component. - -```tsx -interface ProtectedRouteProps { - children: React.ReactElement; - fallback?: React.ReactElement; - redirectTo?: string; - showLoading?: boolean; - loadingElement?: React.ReactElement; -} -``` - -**Props:** - -- `children` - The component to render when authenticated -- `fallback` - Custom component to render when not authenticated (takes precedence over redirectTo) -- `redirectTo` - URL to redirect to when not authenticated (required unless fallback is provided) -- `showLoading` - Whether to show loading state (default: `true`) -- `loadingElement` - Custom loading component - -**Note:** Either `fallback` or `redirectTo` must be provided to handle unauthenticated users. - -#### withAuthentication - -Higher-order component that wraps any component with authentication protection. - -```tsx -import { withAuthentication } from '@asgardeo/react-router'; - -const Dashboard = () =>
Protected Dashboard
; - -const ProtectedDashboard = withAuthentication(Dashboard, { - redirectTo: '/login' -}); - -// With role-based access -const AdminPanel = withAuthentication(AdminPanelComponent, { - additionalCheck: (authContext) => { - return authContext.user?.groups?.includes('admin'); - }, - fallback:
Access denied
-}); -``` - -### Hooks - -#### useAuthGuard - -Hook that provides authentication guard functionality for routes. - -```tsx -import { useAuthGuard } from '@asgardeo/react-router'; - -function Dashboard() { - const { isAllowed, isLoading } = useAuthGuard({ - redirectTo: '/login' - }); - - if (isLoading) return
Loading...
; - if (!isAllowed) return null; // Will redirect - - return
Protected Dashboard Content
; -} -``` - -**Options:** - -- `redirectTo` - Path to redirect when not authenticated (default: `'/login'`) -- `preserveReturnUrl` - Whether to preserve current location as return URL (default: `true`) -- `additionalCheck` - Additional authorization check function -- `immediate` - Whether to check immediately on mount (default: `true`) - -**Returns:** - -- `isAllowed` - Whether user can access the route -- `isLoading` - Whether authentication is being checked -- `isAuthenticated` - Whether user is signed in -- `meetsAdditionalChecks` - Whether additional checks pass -- `authContext` - Full Asgardeo authentication context -- `checkAuth()` - Function to manually trigger auth check - -#### useReturnUrl - -Hook for handling return URLs after authentication. - -```tsx -import { useReturnUrl } from '@asgardeo/react-router'; -import { useAsgardeo } from '@asgardeo/react'; - -function LoginPage() { - const { returnTo, navigateToReturnUrl } = useReturnUrl(); - const { signIn } = useAsgardeo(); - - const handleSignIn = async () => { - await signIn(); - navigateToReturnUrl(); // Redirects to original destination - }; - - return ( -
- - {returnTo &&

You'll be redirected to: {returnTo}

} -
- ); -} -``` - -**Returns:** - -- `returnTo` - The URL to return to after authentication -- `navigateToReturnUrl(fallback?)` - Function to navigate to return URL -- `hasReturnUrl` - Whether a return URL is available - -## Advanced Usage - -### Role-Based Access Control - -```tsx -import { withAuthentication } from '@asgardeo/react-router'; - -const AdminPanel = withAuthentication(AdminPanelComponent, { - additionalCheck: (authContext) => { - const userRoles = authContext.user?.groups || []; - return userRoles.includes('admin') || userRoles.includes('moderator'); - }, - fallback: ( -
-

Access Denied

-

You don't have permission to access this page.

-
- ) -}); -``` - -### Custom Authentication Flow - -```tsx -import { useAuthGuard } from '@asgardeo/react-router'; - -function CustomProtectedPage() { - const { isAllowed, authContext, checkAuth } = useAuthGuard({ - immediate: false, // Don't redirect immediately - additionalCheck: (auth) => auth.user?.email_verified === true - }); - - if (!authContext.isSignedIn) { - return ( -
-

Sign In Required

- -
- ); - } - - if (!authContext.user?.email_verified) { - return ( -
-

Email Verification Required

-

Please verify your email before accessing this page.

-
- ); - } - - return
Protected Content
; -} -``` - -### Integration with Layouts +### 3. Integration with Layouts ```tsx import { ProtectedRoute } from '@asgardeo/react-router'; @@ -350,21 +174,47 @@ function App() { } ``` +## API Reference + +### Components + +#### ProtectedRoute + +A component that protects routes based on authentication status. Should be used as the element prop of a Route component. + +```tsx +interface ProtectedRouteProps { + children: React.ReactElement; + fallback?: React.ReactElement; + redirectTo?: string; + loader?: React.ReactNode; +} +``` + +**Props:** + +- `children` - The component to render when authenticated +- `fallback` - Custom component to render when not authenticated (takes precedence over redirectTo) +- `redirectTo` - URL to redirect to when not authenticated (required unless fallback is provided) +- `loader` - Custom loading component to render while authentication status is being determined + +**Note:** Either `fallback` or `redirectTo` must be provided to handle unauthenticated users. + ## Examples -Check out our [examples directory](./examples) for complete working examples: +Check out our sample applications in the repository: -- [Basic Protected Routes](./examples/basic) -- [Role-Based Access Control](./examples/rbac) -- [Custom Authentication Flow](./examples/custom-flow) -- [Integration with Next.js](./examples/nextjs) +- [React Sample](../../samples/asgardeo-react) - Complete React application with Asgardeo authentication +- [Next.js Sample](../../samples/asgardeo-nextjs) - Next.js application example +- [Teamspace React](../../samples/teamspace-react) - Team collaboration app with React +- [Teamspace Next.js](../../samples/teamspace-nextjs) - Team collaboration app with Next.js ## TypeScript Support -This package is written in TypeScript and provides comprehensive type definitions. All components and hooks are fully typed for the best development experience. +This package is written in TypeScript and provides comprehensive type definitions. All components are fully typed for the best development experience. ```tsx -import type { ProtectedRouteProps, WithAuthenticationOptions } from '@asgardeo/react-router'; +import type { ProtectedRouteProps } from '@asgardeo/react-router'; ``` ## Contributing diff --git a/packages/react-router/package.json b/packages/react-router/package.json index fcb759dd..568dd23e 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,5 +1,4 @@ { - "private": true, "name": "@asgardeo/react-router", "version": "0.0.0", "description": "React Router integration for Asgardeo React SDK with protected routes.", @@ -46,7 +45,6 @@ "devDependencies": { "@types/node": "^22.15.3", "@types/react": "^19.1.4", - "@types/react-router-dom": "^5.3.3", "@wso2/eslint-plugin": "catalog:", "@wso2/prettier-config": "catalog:", "esbuild-plugin-preserve-directives": "^0.0.11", @@ -54,16 +52,15 @@ "eslint": "8.57.0", "prettier": "^2.6.2", "react": "^19.1.0", - "react-router-dom": "^6.30.0", + "react-router": "^7.6.3", "rimraf": "^6.0.1", "typescript": "~5.7.2", "vitest": "^3.1.3" }, "peerDependencies": { "@asgardeo/react": "workspace:^", - "@types/react": ">=16.8.0", "react": ">=16.8.0", - "react-router-dom": ">=6.0.0" + "react-router": ">=6.0.0" }, "dependencies": { "tslib": "^2.8.1" diff --git a/packages/react-router/src/components/ProtectedRoute.tsx b/packages/react-router/src/components/ProtectedRoute.tsx index 6e4d9add..0b8a2521 100644 --- a/packages/react-router/src/components/ProtectedRoute.tsx +++ b/packages/react-router/src/components/ProtectedRoute.tsx @@ -1,5 +1,8 @@ /** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * Copyright (import React, {FC, ReactElement, ReactNode} from 'react'; +import {Navigate} from 'react-router'; +import {useAsgardeo} from '@asgardeo/react'; +import {AsgardeoRuntimeError} from '@asgardeo/browser';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 @@ -16,9 +19,9 @@ * under the License. */ -import React from 'react'; -import {Navigate} from 'react-router-dom'; -import {useAsgardeo} from '@asgardeo/react'; +import {FC, ReactElement, ReactNode} from 'react'; +import {Navigate} from 'react-router'; +import {useAsgardeo, AsgardeoRuntimeError} from '@asgardeo/react'; /** * Props for the ProtectedRoute component. @@ -27,26 +30,21 @@ export interface ProtectedRouteProps { /** * The element to render when the user is authenticated. */ - children: React.ReactElement; + children: ReactElement; /** * Custom fallback element to render when the user is not authenticated. * If provided, this takes precedence over redirectTo. */ - fallback?: React.ReactElement; + fallback?: ReactElement; /** * URL to redirect to when the user is not authenticated. * Required unless a fallback element is provided. */ redirectTo?: string; - /** - * Whether to show a loading state while authentication status is being determined. - * @default true - */ - showLoading?: boolean; /** * Custom loading element to render while authentication status is being determined. */ - loadingElement?: React.ReactElement; + loader?: ReactNode; } /** @@ -83,33 +81,18 @@ export interface ProtectedRouteProps { * /> * ``` */ -const ProtectedRoute: React.FC = ({ - children, - fallback, - redirectTo, - showLoading = true, - loadingElement, -}) => { - const {isSignedIn, isLoading, signIn} = useAsgardeo(); +const ProtectedRoute: FC = ({children, fallback, redirectTo, loader = null}) => { + const {isSignedIn, isLoading} = useAsgardeo(); - // Show loading state while authentication status is being determined - if (isLoading && showLoading) { - if (loadingElement) { - return loadingElement; - } - return ( -
-
Loading...
-
- ); + // Always wait for loading to finish before making authentication decisions + if (isLoading) { + return loader; } - // If user is authenticated, render the protected content if (isSignedIn) { return children; } - // If user is not authenticated, handle fallback/redirect if (fallback) { return fallback; } @@ -118,9 +101,11 @@ const ProtectedRoute: React.FC = ({ return ; } - // If neither fallback nor redirectTo is provided, throw an error - throw new Error( - 'ProtectedRoute: Either "fallback" or "redirectTo" prop must be provided to handle unauthenticated users.', + throw new AsgardeoRuntimeError( + '"fallback" or "redirectTo" prop is required.', + 'ProtectedRoute-ValidationError-001', + 'react-router', + 'Either "fallback" or "redirectTo" prop must be provided to handle unauthenticated users.', ); }; diff --git a/packages/react-router/src/components/withAuthentication.tsx b/packages/react-router/src/components/withAuthentication.tsx deleted file mode 100644 index dd8e4e63..00000000 --- a/packages/react-router/src/components/withAuthentication.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/** - * 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. - */ - -import React from 'react'; -import {Navigate} from 'react-router-dom'; -import {useAsgardeo} from '@asgardeo/react'; - -/** - * Options for the withAuthentication higher-order component. - */ -export interface WithAuthenticationOptions { - /** - * Custom fallback element to render when the user is not authenticated. - */ - fallback?: React.ReactElement; - /** - * URL to redirect to when the user is not authenticated. - */ - redirectTo?: string; - /** - * Whether to show a loading state while authentication status is being determined. - * @default true - */ - showLoading?: boolean; - /** - * Custom loading element to render while authentication status is being determined. - */ - loadingElement?: React.ReactElement; - /** - * Additional condition that must be met for the user to access the component. - * This function receives the authentication context and should return true if access is allowed. - */ - additionalCheck?: (authContext: ReturnType) => boolean; -} - -/** - * Higher-order component that wraps a component with authentication protection. - * - * This HOC can be used to protect any React component, not just Route components. - * It provides more flexibility for complex authentication scenarios. - * - * @param WrappedComponent - The component to protect with authentication - * @param options - Configuration options for the authentication protection - * - * @example - * ```tsx - * import { withAuthentication } from '@asgardeo/react-router'; - * - * const Dashboard = () =>
Protected Dashboard
; - * - * const ProtectedDashboard = withAuthentication(Dashboard, { - * redirectTo: '/login' - * }); - * ``` - * - * @example With additional checks - * ```tsx - * const AdminPanel = withAuthentication(AdminPanelComponent, { - * additionalCheck: (authContext) => { - * // Only allow users with admin role - * return authContext.user?.groups?.includes('admin'); - * }, - * fallback:
You don't have permission to access this page
- * }); - * ``` - */ -const withAuthentication =

( - WrappedComponent: React.ComponentType

, - options: WithAuthenticationOptions = {}, -): React.FC

=> { - const {fallback, redirectTo, showLoading = true, loadingElement, additionalCheck} = options; - - const AuthenticatedComponent: React.FC

= props => { - const authContext = useAsgardeo(); - const {isSignedIn, isLoading, signIn} = authContext; - - // Show loading state while authentication status is being determined - if (isLoading && showLoading) { - if (loadingElement) { - return loadingElement; - } - return ( -

-
Loading...
-
- ); - } - - // Check if user is authenticated - if (!isSignedIn) { - if (fallback) { - return fallback; - } - - if (redirectTo) { - return ; - } - - // Default behavior: show sign-in prompt - return ( -
-
-

Authentication Required

-

You need to sign in to access this page.

- -
-
- ); - } - - // Check additional conditions if provided - if (additionalCheck && !additionalCheck(authContext)) { - if (fallback) { - return fallback; - } - - return ( -
-
-

Access Denied

-

You don't have permission to access this page.

-
-
- ); - } - - // User is authenticated and meets all conditions, render the wrapped component - return ; - }; - - AuthenticatedComponent.displayName = `withAuthentication(${WrappedComponent.displayName || WrappedComponent.name})`; - - return AuthenticatedComponent; -}; - -export default withAuthentication; diff --git a/packages/react-router/src/hooks/useAuthGuard.ts b/packages/react-router/src/hooks/useAuthGuard.ts deleted file mode 100644 index 8858a35f..00000000 --- a/packages/react-router/src/hooks/useAuthGuard.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * 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. - */ - -import {useEffect} from 'react'; -import {useLocation, useNavigate} from 'react-router-dom'; -import {useAsgardeo} from '@asgardeo/react'; - -/** - * Options for the useAuthGuard hook. - */ -export interface UseAuthGuardOptions { - /** - * Path to redirect to when the user is not authenticated. - * @default '/login' - */ - redirectTo?: string; - /** - * Whether to preserve the current location as a return URL. - * When true, adds a 'returnTo' query parameter with the current path. - * @default true - */ - preserveReturnUrl?: boolean; - /** - * Additional condition that must be met for the user to access the route. - * This function receives the authentication context and should return true if access is allowed. - */ - additionalCheck?: (authContext: ReturnType) => boolean; - /** - * Whether to check authentication status immediately on mount. - * @default true - */ - immediate?: boolean; -} - -/** - * Hook that provides authentication guard functionality for routes. - * - * This hook can be used within any component to enforce authentication - * and optionally redirect unauthenticated users. - * - * @param options - Configuration options for the authentication guard - * - * @returns Object containing authentication status and helper functions - * - * @example - * ```tsx - * import { useAuthGuard } from '@asgardeo/react-router'; - * - * function Dashboard() { - * const { isAllowed, isLoading } = useAuthGuard({ - * redirectTo: '/login' - * }); - * - * if (isLoading) return
Loading...
; - * if (!isAllowed) return null; // Will redirect - * - * return
Protected Dashboard Content
; - * } - * ``` - * - * @example With additional checks - * ```tsx - * function AdminPanel() { - * const { isAllowed, authContext } = useAuthGuard({ - * additionalCheck: (auth) => auth.user?.groups?.includes('admin'), - * redirectTo: '/unauthorized' - * }); - * - * if (!isAllowed) return null; - * - * return
Admin Panel
; - * } - * ``` - */ -export const useAuthGuard = (options: UseAuthGuardOptions = {}) => { - const {redirectTo = '/login', preserveReturnUrl = true, additionalCheck, immediate = true} = options; - - const authContext = useAsgardeo(); - const {isSignedIn, isLoading} = authContext; - const navigate = useNavigate(); - const location = useLocation(); - - const isAuthenticated = isSignedIn; - const meetsAdditionalChecks = additionalCheck ? additionalCheck(authContext) : true; - const isAllowed = isAuthenticated && meetsAdditionalChecks; - - useEffect(() => { - if (!immediate || isLoading) { - return; - } - - if (!isAuthenticated) { - const redirectUrl = preserveReturnUrl - ? `${redirectTo}?returnTo=${encodeURIComponent(location.pathname + location.search)}` - : redirectTo; - - navigate(redirectUrl, {replace: true}); - return; - } - - if (!meetsAdditionalChecks) { - navigate(redirectTo, {replace: true}); - } - }, [isAuthenticated, meetsAdditionalChecks, immediate, isLoading, navigate, redirectTo, preserveReturnUrl, location]); - - return { - /** - * Whether the user is allowed to access the current route. - */ - isAllowed, - /** - * Whether authentication status is still being determined. - */ - isLoading, - /** - * Whether the user is authenticated. - */ - isAuthenticated, - /** - * Whether the user meets additional authorization checks. - */ - meetsAdditionalChecks, - /** - * The full authentication context from useAsgardeo. - */ - authContext, - /** - * Function to manually trigger the authentication check and redirect. - */ - checkAuth: () => { - if (!isAuthenticated) { - const redirectUrl = preserveReturnUrl - ? `${redirectTo}?returnTo=${encodeURIComponent(location.pathname + location.search)}` - : redirectTo; - - navigate(redirectUrl, {replace: true}); - return; - } - - if (!meetsAdditionalChecks) { - navigate(redirectTo, {replace: true}); - } - }, - }; -}; - -/** - * Hook that provides functionality for handling return URLs after authentication. - * - * This hook is typically used on login/authentication pages to redirect users - * back to where they were trying to go before being redirected to sign in. - * - * @example - * ```tsx - * import { useReturnUrl } from '@asgardeo/react-router'; - * - * function LoginPage() { - * const { returnTo, navigateToReturnUrl } = useReturnUrl(); - * const { signIn } = useAsgardeo(); - * - * const handleSignIn = async () => { - * await signIn(); - * navigateToReturnUrl(); // Redirects to original destination - * }; - * - * return ( - *
- * - * {returnTo &&

You'll be redirected to: {returnTo}

} - *
- * ); - * } - * ``` - */ -export const useReturnUrl = () => { - const navigate = useNavigate(); - const location = useLocation(); - - const searchParams = new URLSearchParams(location.search); - const returnTo = searchParams.get('returnTo'); - - const navigateToReturnUrl = (fallbackPath = '/') => { - const destination = returnTo || fallbackPath; - navigate(destination, {replace: true}); - }; - - return { - /** - * The URL to return to after authentication, if available. - */ - returnTo, - /** - * Function to navigate to the return URL or a fallback path. - */ - navigateToReturnUrl, - /** - * Whether a return URL is available. - */ - hasReturnUrl: Boolean(returnTo), - }; -}; - -export default useAuthGuard; diff --git a/packages/react-router/src/index.ts b/packages/react-router/src/index.ts index ef47bf9c..3b4f0cbb 100644 --- a/packages/react-router/src/index.ts +++ b/packages/react-router/src/index.ts @@ -16,13 +16,5 @@ * under the License. */ -// Components export {default as ProtectedRoute} from './components/ProtectedRoute'; export * from './components/ProtectedRoute'; - -export {default as withAuthentication} from './components/withAuthentication'; -export * from './components/withAuthentication'; - -// Hooks -export {default as useAuthGuard, useReturnUrl} from './hooks/useAuthGuard'; -export * from './hooks/useAuthGuard'; From 6c8f5c4e4dbe0e5a6e6d5eafd0082f19c5f95a5d Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 13:45:06 +0530 Subject: [PATCH 2/6] fix(react): enhance loading state management in Asgardeo client and provider --- packages/react/API.md | 2 +- packages/react/src/AsgardeoReactClient.ts | 128 +++++++++++------- .../contexts/Asgardeo/AsgardeoProvider.tsx | 56 ++++++-- packages/react/src/index.ts | 2 + 4 files changed, 126 insertions(+), 62 deletions(-) diff --git a/packages/react/API.md b/packages/react/API.md index bd295764..e0d7ee9f 100644 --- a/packages/react/API.md +++ b/packages/react/API.md @@ -33,7 +33,7 @@ The root provider component that configures the Asgardeo SDK and provides authen | `clientId` | `string` | Yes | Your application's client ID | | `afterSignInUrl` | `string` | No | URL to redirect after sign in (defaults to current URL) | | `afterSignOutUrl` | `string` | No | URL to redirect after sign out (defaults to current URL) | -| `scopes` | `string[] | string` | No | OAuth scopes to request (defaults to `['openid', 'profile']`) | +| `scopes` | `string[] \| string` | No | OAuth scopes to request (defaults to `'openid profile internal_login'`) | | `storage` | `'localStorage' \| 'sessionStorage'` | No | Storage mechanism for tokens (defaults to `'localStorage'`) | #### Example diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index 60e38fe1..fcb46ea1 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -55,6 +55,7 @@ import getAllOrganizations from './api/getAllOrganizations'; */ class AsgardeoReactClient extends AsgardeoBrowserClient { private asgardeo: AuthAPI; + private _isLoading: boolean = false; constructor() { super(); @@ -63,6 +64,29 @@ class AsgardeoReactClient e this.asgardeo = new AuthAPI(); } + /** + * Set the loading state of the client + * @param loading - Boolean indicating if the client is in a loading state + */ + private setLoading(loading: boolean): void { + this._isLoading = loading; + } + + /** + * Wrap async operations with loading state management + * @param operation - The async operation to execute + * @returns Promise with the result of the operation + */ + private async withLoading(operation: () => Promise): Promise { + this.setLoading(true); + try { + const result = await operation(); + return result; + } finally { + this.setLoading(false); + } + } + override initialize(config: AsgardeoReactConfig): Promise { let resolvedOrganizationHandle: string | undefined = config?.organizationHandle; @@ -70,7 +94,9 @@ class AsgardeoReactClient e resolvedOrganizationHandle = deriveOrganizationHandleFromBaseUrl(config?.baseUrl); } - return this.asgardeo.init({...config, organizationHandle: resolvedOrganizationHandle} as any); + return this.withLoading(async () => { + return this.asgardeo.init({...config, organizationHandle: resolvedOrganizationHandle} as any); + }); } override async updateUserProfile(payload: any, userId?: string): Promise { @@ -181,51 +207,53 @@ class AsgardeoReactClient e }; } - override async switchOrganization(organization: Organization, sessionId?: string): Promise { - try { - const configData = await this.asgardeo.getConfigData(); - const scopes = configData?.scopes; - - if (!organization.id) { + override async switchOrganization(organization: Organization, sessionId?: string): Promise { + return this.withLoading(async () => { + try { + const configData = await this.asgardeo.getConfigData(); + const scopes = configData?.scopes; + + if (!organization.id) { + throw new AsgardeoRuntimeError( + 'Organization ID is required for switching organizations', + 'react-AsgardeoReactClient-SwitchOrganizationError-001', + 'react', + 'The organization object must contain a valid ID to perform the organization switch.', + ); + } + + const exchangeConfig = { + attachToken: false, + data: { + client_id: '{{clientId}}', + grant_type: 'organization_switch', + scope: '{{scopes}}', + switching_organization: organization.id, + token: '{{accessToken}}', + }, + id: 'organization-switch', + returnsSession: true, + signInRequired: true, + }; + + return (await this.asgardeo.exchangeToken( + exchangeConfig, + (user: User) => {}, + () => null, + )) as TokenResponse | Response; + } catch (error) { throw new AsgardeoRuntimeError( - 'Organization ID is required for switching organizations', - 'react-AsgardeoReactClient-SwitchOrganizationError-001', + `Failed to switch organization: ${error.message || error}`, + 'react-AsgardeoReactClient-SwitchOrganizationError-003', 'react', - 'The organization object must contain a valid ID to perform the organization switch.', + 'An error occurred while switching to the specified organization. Please try again.', ); } - - const exchangeConfig = { - attachToken: false, - data: { - client_id: '{{clientId}}', - grant_type: 'organization_switch', - scope: '{{scopes}}', - switching_organization: organization.id, - token: '{{accessToken}}', - }, - id: 'organization-switch', - returnsSession: true, - signInRequired: true, - }; - - return await this.asgardeo.exchangeToken( - exchangeConfig, - (user: User) => {}, - () => null, - ) as TokenResponse | Response; - } catch (error) { - throw new AsgardeoRuntimeError( - `Failed to switch organization: ${error.message || error}`, - 'react-AsgardeoReactClient-SwitchOrganizationError-003', - 'react', - 'An error occurred while switching to the specified organization. Please try again.', - ); - } + }); } override isLoading(): boolean { - return this.asgardeo.isLoading(); + return this._isLoading || this.asgardeo.isLoading(); } async isInitialized(): Promise { @@ -252,17 +280,19 @@ class AsgardeoReactClient e onSignInSuccess?: (afterSignInUrl: string) => void, ): Promise; override async signIn(...args: any[]): Promise { - const arg1 = args[0]; - const arg2 = args[1]; - - if (typeof arg1 === 'object' && 'flowId' in arg1 && typeof arg2 === 'object' && 'url' in arg2) { - return executeEmbeddedSignInFlow({ - payload: arg1, - url: arg2.url, - }); - } + return this.withLoading(async () => { + const arg1 = args[0]; + const arg2 = args[1]; + + if (typeof arg1 === 'object' && 'flowId' in arg1 && typeof arg2 === 'object' && 'url' in arg2) { + return executeEmbeddedSignInFlow({ + payload: arg1, + url: arg2.url, + }); + } - return (await this.asgardeo.signIn(arg1 as any)) as unknown as Promise; + return (await this.asgardeo.signIn(arg1 as any)) as unknown as Promise; + }); } override signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise; diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index 0a412568..5ce049e7 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -69,6 +69,7 @@ const AsgardeoProvider: FC> = ({ const [isSignedInSync, setIsSignedInSync] = useState(false); const [isInitializedSync, setIsInitializedSync] = useState(false); + const [isLoadingSync, setIsLoadingSync] = useState(true); const [myOrganizations, setMyOrganizations] = useState([]); const [userProfile, setUserProfile] = useState(null); @@ -200,20 +201,45 @@ const AsgardeoProvider: FC> = ({ })(); }, [asgardeo]); + /** + * Track loading state changes from the Asgardeo client + */ + useEffect(() => { + const checkLoadingState = (): void => { + const loadingState = asgardeo.isLoading(); + setIsLoadingSync(loadingState); + }; + + // Initial check + checkLoadingState(); + + // Set up an interval to check for loading state changes + const interval = setInterval(checkLoadingState, 100); + + return (): void => { + clearInterval(interval); + }; + }, [asgardeo]); + const updateSession = async (): Promise => { - let _baseUrl: string = baseUrl; + try { + setIsLoadingSync(true); + let _baseUrl: string = baseUrl; + + // If there's a `user_org` claim in the ID token, + // Treat this login as a organization login. + if ((await asgardeo.getDecodedIdToken())?.['user_org']) { + _baseUrl = `${(await asgardeo.getConfiguration()).baseUrl}/o`; + setBaseUrl(_baseUrl); + } - // If there's a `user_org` claim in the ID token, - // Treat this login as a organization login. - if ((await asgardeo.getDecodedIdToken())?.['user_org']) { - _baseUrl = `${(await asgardeo.getConfiguration()).baseUrl}/o`; - setBaseUrl(_baseUrl); + setUser(await asgardeo.getUser({baseUrl: _baseUrl})); + setUserProfile(await asgardeo.getUserProfile({baseUrl: _baseUrl})); + setCurrentOrganization(await asgardeo.getCurrentOrganization()); + setMyOrganizations(await asgardeo.getMyOrganizations()); + } finally { + setIsLoadingSync(asgardeo.isLoading()); } - - setUser(await asgardeo.getUser({baseUrl: _baseUrl})); - setUserProfile(await asgardeo.getUserProfile({baseUrl: _baseUrl})); - setCurrentOrganization(await asgardeo.getCurrentOrganization()); - setMyOrganizations(await asgardeo.getMyOrganizations()); }; // Branding fetch function @@ -275,6 +301,7 @@ const AsgardeoProvider: FC> = ({ const signIn = async (...args: any): Promise => { try { + setIsLoadingSync(true); const response: User = await asgardeo.signIn(...args); if (await asgardeo.isSignedIn()) { @@ -284,6 +311,8 @@ const AsgardeoProvider: FC> = ({ return response; } catch (error) { throw new Error(`Error while signing in: ${error}`); + } finally { + setIsLoadingSync(asgardeo.isLoading()); } }; @@ -305,6 +334,7 @@ const AsgardeoProvider: FC> = ({ const switchOrganization = async (organization: Organization): Promise => { try { + setIsLoadingSync(true); await asgardeo.switchOrganization(organization); if (await asgardeo.isSignedIn()) { @@ -317,6 +347,8 @@ const AsgardeoProvider: FC> = ({ 'react', 'An error occurred while switching to the specified organization.', ); + } finally { + setIsLoadingSync(asgardeo.isLoading()); } }; @@ -346,7 +378,7 @@ const AsgardeoProvider: FC> = ({ afterSignInUrl, baseUrl, isInitialized: isInitializedSync, - isLoading: asgardeo.isLoading(), + isLoading: isLoadingSync, isSignedIn: isSignedInSync, organization: currentOrganization, signIn, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index c7b9e73a..fafa46a8 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -272,3 +272,5 @@ export {default as getSchemas, GetSchemasConfig} from './api/getSchemas'; export {default as updateMeProfile, UpdateMeProfileConfig} from './api/updateMeProfile'; export {default as getMeProfile} from './api/getScim2Me'; export * from './api/getScim2Me'; + +export {AsgardeoRuntimeError} from '@asgardeo/browser'; From 58ca69f1d38e2bc71ce52215b988173ff986d728 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 13:45:29 +0530 Subject: [PATCH 3/6] chore(samples): migrate from react-router-dom to react-router and update related components --- pnpm-lock.yaml | 85 +++++++------------ samples/teamspace-react/package.json | 2 +- samples/teamspace-react/src/App.tsx | 35 +++++--- .../Header/AuthenticatedMobileMenu.tsx | 2 +- .../Header/AuthenticatedNavigation.tsx | 2 +- .../src/components/Header/Logo.tsx | 2 +- .../Header/OrganizationSwitcher.tsx | 2 +- .../src/components/Header/PublicActions.tsx | 2 +- .../src/components/Header/UserDropdown.tsx | 2 +- .../src/pages/CreateOrganizationPage.tsx | 2 +- .../teamspace-react/src/pages/LandingPage.tsx | 2 +- .../src/pages/Organizations.tsx | 2 +- samples/teamspace-react/src/pages/Profile.tsx | 2 +- .../teamspace-react/src/pages/SignUpPage.tsx | 2 +- 14 files changed, 63 insertions(+), 81 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9dac0a2..b5a41242 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -402,9 +402,6 @@ importers: '@types/react': specifier: ^19.1.4 version: 19.1.5 - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 '@wso2/eslint-plugin': specifier: 'catalog:' version: https://gitpkg.now.sh/brionmario/wso2-ui-configs/packages/eslint-plugin?a1fc6eb570653c999828aea9f5027cba06af4391(eslint@8.57.0)(typescript@5.7.3) @@ -426,9 +423,9 @@ importers: react: specifier: ^19.1.0 version: 19.1.0 - react-router-dom: - specifier: ^6.30.0 - version: 6.30.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: + specifier: ^7.6.3 + version: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -577,9 +574,9 @@ importers: react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) - react-router-dom: - specifier: ^6.20.0 - version: 6.30.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-router: + specifier: ^7.6.3 + version: 7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: ^3.3.0 version: 3.3.0 @@ -1791,10 +1788,6 @@ packages: '@types/react': optional: true - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} - engines: {node: '>=14.0.0'} - '@rolldown/pluginutils@1.0.0-beta.9': resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} @@ -2128,9 +2121,6 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/history@4.7.11': - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2178,12 +2168,6 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react-router-dom@5.3.3': - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} - - '@types/react-router@5.1.20': - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@18.3.23': resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} @@ -3059,6 +3043,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -5485,18 +5473,15 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} - engines: {node: '>=14.0.0'} + react-router@7.6.3: + resolution: {integrity: sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==} + engines: {node: '>=20.0.0'} peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} @@ -5715,6 +5700,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -7690,8 +7678,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.5 - '@remix-run/router@1.23.0': {} - '@rolldown/pluginutils@1.0.0-beta.9': {} '@rollup/plugin-commonjs@25.0.8(rollup@4.40.2)': @@ -7989,8 +7975,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/history@4.7.11': {} - '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -8036,17 +8020,6 @@ snapshots: dependencies: '@types/react': 19.1.5 - '@types/react-router-dom@5.3.3': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.1.5 - '@types/react-router': 5.1.20 - - '@types/react-router@5.1.20': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.1.5 - '@types/react@18.3.23': dependencies: '@types/prop-types': 15.7.15 @@ -9273,6 +9246,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 @@ -12077,17 +12052,13 @@ snapshots: react-refresh@0.17.0: {} - react-router-dom@6.30.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-router@7.6.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@remix-run/router': 1.23.0 + cookie: 1.0.2 react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: react-dom: 19.1.0(react@19.1.0) - react-router: 6.30.1(react@19.1.0) - - react-router@6.30.1(react@19.1.0): - dependencies: - '@remix-run/router': 1.23.0 - react: 19.1.0 react@18.3.1: dependencies: @@ -12387,6 +12358,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 diff --git a/samples/teamspace-react/package.json b/samples/teamspace-react/package.json index 3a558811..bddc568e 100644 --- a/samples/teamspace-react/package.json +++ b/samples/teamspace-react/package.json @@ -19,7 +19,7 @@ "lucide-react": "^0.294.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-router-dom": "^6.20.0", + "react-router": "^7.6.3", "tailwind-merge": "^3.3.0" }, "devDependencies": { diff --git a/samples/teamspace-react/src/App.tsx b/samples/teamspace-react/src/App.tsx index a6c78f07..54577d6b 100644 --- a/samples/teamspace-react/src/App.tsx +++ b/samples/teamspace-react/src/App.tsx @@ -1,6 +1,6 @@ 'use client'; -import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; +import {BrowserRouter as Router, Routes, Route} from 'react-router'; import {useState, createContext, useContext} from 'react'; import DashboardPage from './pages/Dashboard'; import ProfilePage from './pages/Profile'; @@ -12,6 +12,7 @@ import LandingLayout from './layouts/LandingLayout'; import DashboardLayout from './layouts/DashboardLayout'; import AuthenticatedLayout from './layouts/AuthenticatedLayout'; import SignUpPage from './pages/SignUpPage'; +import {ProtectedRoute} from '@asgardeo/react-router'; // Types export interface User { @@ -129,33 +130,41 @@ function App() { - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> - - + + + + + } /> diff --git a/samples/teamspace-react/src/components/Header/AuthenticatedMobileMenu.tsx b/samples/teamspace-react/src/components/Header/AuthenticatedMobileMenu.tsx index 518f6eaf..26e9fb49 100644 --- a/samples/teamspace-react/src/components/Header/AuthenticatedMobileMenu.tsx +++ b/samples/teamspace-react/src/components/Header/AuthenticatedMobileMenu.tsx @@ -1,7 +1,7 @@ 'use client'; import {useState} from 'react'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; import {Menu, X, Home, Briefcase, Users as UsersIcon} from 'lucide-react'; interface AuthenticatedMobileMenuProps { diff --git a/samples/teamspace-react/src/components/Header/AuthenticatedNavigation.tsx b/samples/teamspace-react/src/components/Header/AuthenticatedNavigation.tsx index 34bb455e..161a9736 100644 --- a/samples/teamspace-react/src/components/Header/AuthenticatedNavigation.tsx +++ b/samples/teamspace-react/src/components/Header/AuthenticatedNavigation.tsx @@ -1,6 +1,6 @@ 'use client'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; interface AuthenticatedNavigationProps { className?: string; diff --git a/samples/teamspace-react/src/components/Header/Logo.tsx b/samples/teamspace-react/src/components/Header/Logo.tsx index 19249594..6dbd9da9 100644 --- a/samples/teamspace-react/src/components/Header/Logo.tsx +++ b/samples/teamspace-react/src/components/Header/Logo.tsx @@ -1,6 +1,6 @@ 'use client'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; import {Users} from 'lucide-react'; interface LogoProps { diff --git a/samples/teamspace-react/src/components/Header/OrganizationSwitcher.tsx b/samples/teamspace-react/src/components/Header/OrganizationSwitcher.tsx index 1367e469..777d99dc 100644 --- a/samples/teamspace-react/src/components/Header/OrganizationSwitcher.tsx +++ b/samples/teamspace-react/src/components/Header/OrganizationSwitcher.tsx @@ -1,7 +1,7 @@ 'use client'; import {useState, useRef, useEffect} from 'react'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; import {ChevronDown, Plus, Check, Building2} from 'lucide-react'; import {useApp} from '../../App'; diff --git a/samples/teamspace-react/src/components/Header/PublicActions.tsx b/samples/teamspace-react/src/components/Header/PublicActions.tsx index 385e50b3..ca939c9b 100644 --- a/samples/teamspace-react/src/components/Header/PublicActions.tsx +++ b/samples/teamspace-react/src/components/Header/PublicActions.tsx @@ -1,6 +1,6 @@ 'use client'; -import {useNavigate} from 'react-router-dom'; +import {useNavigate} from 'react-router'; import {SignInButton, SignUpButton} from '@asgardeo/react'; import {Button} from '../ui/button'; diff --git a/samples/teamspace-react/src/components/Header/UserDropdown.tsx b/samples/teamspace-react/src/components/Header/UserDropdown.tsx index cede9101..e0aed1a0 100644 --- a/samples/teamspace-react/src/components/Header/UserDropdown.tsx +++ b/samples/teamspace-react/src/components/Header/UserDropdown.tsx @@ -3,7 +3,7 @@ import {ChevronDown, LogOut, Settings, UserIcon, Workflow, LayoutDashboard} from 'lucide-react'; import {UserDropdown as _UserDropdown, SignOutButton, UserProfile} from '@asgardeo/react';; import {useState, useRef} from 'react'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; export type UserDropdownProps = { mode?: 'custom' | 'default'; diff --git a/samples/teamspace-react/src/pages/CreateOrganizationPage.tsx b/samples/teamspace-react/src/pages/CreateOrganizationPage.tsx index 14ce7209..5b615611 100644 --- a/samples/teamspace-react/src/pages/CreateOrganizationPage.tsx +++ b/samples/teamspace-react/src/pages/CreateOrganizationPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import {useNavigate} from 'react-router-dom'; +import {useNavigate} from 'react-router'; import {CreateOrganization} from '@asgardeo/react'; import {ArrowLeft} from 'lucide-react'; diff --git a/samples/teamspace-react/src/pages/LandingPage.tsx b/samples/teamspace-react/src/pages/LandingPage.tsx index ff0c7b58..6f5a0c2c 100644 --- a/samples/teamspace-react/src/pages/LandingPage.tsx +++ b/samples/teamspace-react/src/pages/LandingPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import {Link} from 'react-router-dom'; +import {Link} from 'react-router'; import { Users, MessageSquare, diff --git a/samples/teamspace-react/src/pages/Organizations.tsx b/samples/teamspace-react/src/pages/Organizations.tsx index 7a1aefd5..38695f72 100644 --- a/samples/teamspace-react/src/pages/Organizations.tsx +++ b/samples/teamspace-react/src/pages/Organizations.tsx @@ -1,6 +1,6 @@ 'use client'; -import {Link, useNavigate} from 'react-router-dom'; +import {Link, useNavigate} from 'react-router'; import {ArrowLeft, Plus} from 'lucide-react'; import {OrganizationList} from '@asgardeo/react'; diff --git a/samples/teamspace-react/src/pages/Profile.tsx b/samples/teamspace-react/src/pages/Profile.tsx index 9b7805db..70c4ce6a 100644 --- a/samples/teamspace-react/src/pages/Profile.tsx +++ b/samples/teamspace-react/src/pages/Profile.tsx @@ -2,7 +2,7 @@ import {UserProfile} from '@asgardeo/react'; import {ArrowLeft} from 'lucide-react'; -import {useNavigate} from 'react-router-dom'; +import {useNavigate} from 'react-router'; export default function Profile() { const navigate = useNavigate(); diff --git a/samples/teamspace-react/src/pages/SignUpPage.tsx b/samples/teamspace-react/src/pages/SignUpPage.tsx index c0f5432f..b9b4adcc 100644 --- a/samples/teamspace-react/src/pages/SignUpPage.tsx +++ b/samples/teamspace-react/src/pages/SignUpPage.tsx @@ -1,7 +1,7 @@ 'use client'; import {SignUp} from '@asgardeo/react'; -import {useNavigate} from 'react-router-dom'; +import {useNavigate} from 'react-router'; export default function SignUpPage() { const navigate = useNavigate(); From 8672db7867c5e4657f94a0f292822f3d603aaae7 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 13:47:47 +0530 Subject: [PATCH 4/6] chore: rename trySignInSilently to signInSilently across the codebase --- packages/browser/src/__legacy__/client.ts | 6 +++--- .../src/__legacy__/clients/main-thread-client.ts | 9 +++++---- .../src/__legacy__/clients/web-worker-client.ts | 6 +++--- .../__legacy__/helpers/authentication-helper.ts | 2 +- packages/browser/src/__legacy__/models/client.ts | 4 ++-- packages/react/src/__temp__/api.ts | 6 +++--- packages/react/src/__temp__/models.ts | 2 +- packages/vue/src/auth-api.ts | 6 +++--- packages/vue/src/plugins/AsgardeoPlugin.ts | 8 ++++---- packages/vue/src/tests/AsgardeoPlugin.test.ts | 4 ++-- packages/vue/src/tests/auth-api.test.ts | 14 +++++++------- packages/vue/src/tests/mocks/mocks.ts | 6 +++--- packages/vue/src/tests/useAsgardeo.test.ts | 2 +- packages/vue/src/types.ts | 2 +- 14 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index caae1d85..0d24c47e 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -428,10 +428,10 @@ export class AsgardeoSPAClient { * * @example *``` - * auth.trySignInSilently() + * auth.signInSilently() *``` */ - public async trySignInSilently( + public async signInSilently( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise { @@ -442,7 +442,7 @@ export class AsgardeoSPAClient { return undefined; } - return this._client?.trySignInSilently(additionalParams, tokenRequestConfig).then((response: User | boolean) => { + return this._client?.signInSilently(additionalParams, tokenRequestConfig).then((response: User | boolean) => { if (this._onSignInCallback && response) { this._onSignInCallback(response as User); } diff --git a/packages/browser/src/__legacy__/clients/main-thread-client.ts b/packages/browser/src/__legacy__/clients/main-thread-client.ts index 2d1a24ed..72573016 100755 --- a/packages/browser/src/__legacy__/clients/main-thread-client.ts +++ b/packages/browser/src/__legacy__/clients/main-thread-client.ts @@ -356,11 +356,11 @@ export const MainThreadClient = async ( * @return {Promise, tokenRequestConfig?: {params: Record}, ): Promise => - _authenticationHelper.trySignInSilently( + _authenticationHelper.signInSilently( constructSilentSignInUrl, requestAccessToken, _sessionManagementHelper, @@ -370,7 +370,8 @@ export const MainThreadClient = async ( const getUser = async (): Promise => _authenticationHelper.getUser(); - const getDecodedIdToken = async (sessionId?: string): Promise => _authenticationHelper.getDecodedIdToken(sessionId); + const getDecodedIdToken = async (sessionId?: string): Promise => + _authenticationHelper.getDecodedIdToken(sessionId); const getCrypto = async (): Promise => _authenticationHelper.getCrypto(); @@ -437,7 +438,7 @@ export const MainThreadClient = async ( setHttpRequestSuccessCallback, signIn, signOut, - trySignInSilently, + signInSilently, reInitialize, }; }; diff --git a/packages/browser/src/__legacy__/clients/web-worker-client.ts b/packages/browser/src/__legacy__/clients/web-worker-client.ts index 9c43b5cb..673526cb 100755 --- a/packages/browser/src/__legacy__/clients/web-worker-client.ts +++ b/packages/browser/src/__legacy__/clients/web-worker-client.ts @@ -429,11 +429,11 @@ export const WebWorkerClient = async ( * @return {Promise, tokenRequestConfig?: {params: Record}, ): Promise => { - return await _authenticationHelper.trySignInSilently( + return await _authenticationHelper.signInSilently( constructSilentSignInUrl, requestAccessToken, _sessionManagementHelper, @@ -859,7 +859,7 @@ export const WebWorkerClient = async ( setHttpRequestSuccessCallback, signIn, signOut, - trySignInSilently, + signInSilently, reInitialize, }; }; diff --git a/packages/browser/src/__legacy__/helpers/authentication-helper.ts b/packages/browser/src/__legacy__/helpers/authentication-helper.ts index 3ed6137b..6a556bf5 100644 --- a/packages/browser/src/__legacy__/helpers/authentication-helper.ts +++ b/packages/browser/src/__legacy__/helpers/authentication-helper.ts @@ -505,7 +505,7 @@ export class AuthenticationHelper) => Promise, requestAccessToken: ( authzCode: string, diff --git a/packages/browser/src/__legacy__/models/client.ts b/packages/browser/src/__legacy__/models/client.ts index c2f65a3e..023f5d1b 100755 --- a/packages/browser/src/__legacy__/models/client.ts +++ b/packages/browser/src/__legacy__/models/client.ts @@ -68,7 +68,7 @@ export interface MainThreadClientInterface { getStorageManager(): Promise>; isSignedIn(): Promise; reInitialize(config: Partial>): Promise; - trySignInSilently( + signInSilently( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise; @@ -107,7 +107,7 @@ export interface WebWorkerClientInterface { setHttpRequestFinishCallback(callback: () => void): void; refreshAccessToken(): Promise; reInitialize(config: Partial>): Promise; - trySignInSilently( + signInSilently( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise; diff --git a/packages/react/src/__temp__/api.ts b/packages/react/src/__temp__/api.ts index 83e26768..4d3bcfa4 100644 --- a/packages/react/src/__temp__/api.ts +++ b/packages/react/src/__temp__/api.ts @@ -426,17 +426,17 @@ class AuthAPI { * * @example *``` - * client.trySignInSilently() + * client.signInSilently() *``` */ - public async trySignInSilently( + public async signInSilently( state: AuthStateInterface, dispatch: (state: AuthStateInterface) => void, additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise { return this._client - .trySignInSilently(additionalParams, tokenRequestConfig) + .signInSilently(additionalParams, tokenRequestConfig) .then(async (response: User | boolean) => { if (!response) { this.updateState({...this.getState(), isLoading: false}); diff --git a/packages/react/src/__temp__/models.ts b/packages/react/src/__temp__/models.ts index 537f7dc0..e188a67b 100644 --- a/packages/react/src/__temp__/models.ts +++ b/packages/react/src/__temp__/models.ts @@ -104,7 +104,7 @@ export interface AuthContextInterface { enableHttpHandler(): Promise; disableHttpHandler(): Promise; reInitialize(config: Partial>): Promise; - trySignInSilently: ( + signInSilently: ( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ) => Promise; diff --git a/packages/vue/src/auth-api.ts b/packages/vue/src/auth-api.ts index 903bccb4..345bb1ac 100644 --- a/packages/vue/src/auth-api.ts +++ b/packages/vue/src/auth-api.ts @@ -380,15 +380,15 @@ class AuthAPI { * * @example * ``` - * client.trySignInSilently(); + * client.signInSilently(); * ``` */ - public async trySignInSilently( + public async signInSilently( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise { return this._client - .trySignInSilently(additionalParams, tokenRequestConfig) + .signInSilently(additionalParams, tokenRequestConfig) .then(async (response: BasicUserInfo | boolean) => { if (!response) { Object.assign(this._authState, {isLoading: false}); diff --git a/packages/vue/src/plugins/AsgardeoPlugin.ts b/packages/vue/src/plugins/AsgardeoPlugin.ts index 9c13aac2..cca6c4dd 100644 --- a/packages/vue/src/plugins/AsgardeoPlugin.ts +++ b/packages/vue/src/plugins/AsgardeoPlugin.ts @@ -72,11 +72,11 @@ export const asgardeoPlugin: Plugin = { } }; - const trySignInSilently = async ( + const signInSilently = async ( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise => - withStateSync(async () => AuthClient.trySignInSilently(additionalParams, tokenRequestConfig)); + withStateSync(async () => AuthClient.signInSilently(additionalParams, tokenRequestConfig)); const checkIsAuthenticated = async (): Promise => withStateSync(async () => { @@ -141,7 +141,7 @@ export const asgardeoPlugin: Plugin = { } if (!config.disableTrySignInSilently) { - await trySignInSilently(); + await signInSilently(); } } catch (err) { error.value = err; @@ -219,7 +219,7 @@ export const asgardeoPlugin: Plugin = { return result; }), state, - trySignInSilently, + signInSilently, reInitialize: async (config: Partial>): Promise => withStateSync(async () => { await AuthClient.reInitialize(config); diff --git a/packages/vue/src/tests/AsgardeoPlugin.test.ts b/packages/vue/src/tests/AsgardeoPlugin.test.ts index 240b76fc..1ce3f713 100644 --- a/packages/vue/src/tests/AsgardeoPlugin.test.ts +++ b/packages/vue/src/tests/AsgardeoPlugin.test.ts @@ -166,9 +166,9 @@ describe('asgardeoPlugin', () => { mockAuthAPI.getState.mockReturnValueOnce(silentSignInState); - await authContext.trySignInSilently(additionalParams, tokenRequestConfig); + await authContext.signInSilently(additionalParams, tokenRequestConfig); - expect(mockAuthAPI.trySignInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); + expect(mockAuthAPI.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); expect(authContext.state).toMatchObject(silentSignInState); }); diff --git a/packages/vue/src/tests/auth-api.test.ts b/packages/vue/src/tests/auth-api.test.ts index 31a3bbd7..781a3733 100644 --- a/packages/vue/src/tests/auth-api.test.ts +++ b/packages/vue/src/tests/auth-api.test.ts @@ -445,14 +445,14 @@ describe('AuthAPI', () => { }); }); - describe('trySignInSilently', () => { - it('should call trySignInSilently on the client and update state on success', async () => { + describe('signInSilently', () => { + it('should call signInSilently on the client and update state on success', async () => { const additionalParams: Record = {prompt: 'none'}; const tokenRequestConfig: {params: Record} = {params: {scope: 'openid profile'}}; - const result: boolean | BasicUserInfo = await authApi.trySignInSilently(additionalParams, tokenRequestConfig); + const result: boolean | BasicUserInfo = await authApi.signInSilently(additionalParams, tokenRequestConfig); - expect(mockClient.trySignInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); + expect(mockClient.signInSilently).toHaveBeenCalledWith(additionalParams, tokenRequestConfig); expect(authApi.getState()).toMatchObject({ allowedScopes: 'openid profile', displayName: 'Test User', @@ -471,11 +471,11 @@ describe('AuthAPI', () => { }); }); - it('should handle false response from trySignInSilently', async () => { - mockClient.trySignInSilently.mockResolvedValueOnce(false); + it('should handle false response from signInSilently', async () => { + mockClient.signInSilently.mockResolvedValueOnce(false); mockClient.isSignedIn.mockResolvedValueOnce(false); - const result: boolean | BasicUserInfo = await authApi.trySignInSilently(); + const result: boolean | BasicUserInfo = await authApi.signInSilently(); expect(result).toBe(false); expect(authApi.getState().isLoading).toBe(false); diff --git a/packages/vue/src/tests/mocks/mocks.ts b/packages/vue/src/tests/mocks/mocks.ts index d31ffbcb..abcf5ea2 100644 --- a/packages/vue/src/tests/mocks/mocks.ts +++ b/packages/vue/src/tests/mocks/mocks.ts @@ -62,7 +62,7 @@ export type MockAuthAPI = { revokeAccessToken: Mock; signIn: Mock; signOut: Mock; - trySignInSilently: Mock; + signInSilently: Mock; reInitialize: Mock; updateState: Mock; }; @@ -108,7 +108,7 @@ export const mockAuthAPI: MockAuthAPI = { username: 'testUser', }), signOut: vi.fn().mockResolvedValue(true), - trySignInSilently: vi.fn().mockResolvedValue(false), + signInSilently: vi.fn().mockResolvedValue(false), reInitialize: vi.fn().mockResolvedValue(undefined), updateState: vi.fn().mockImplementation((newState: AuthStateInterface) => { Object.assign(mockState, newState); @@ -156,7 +156,7 @@ export const mockAsgardeoSPAClient: Partial = { username: 'testUser', }), signOut: vi.fn().mockResolvedValue(true), - trySignInSilently: vi.fn().mockResolvedValue({ + signInSilently: vi.fn().mockResolvedValue({ allowedScopes: 'openid profile', displayName: 'Test User', email: 'test@example.com', diff --git a/packages/vue/src/tests/useAsgardeo.test.ts b/packages/vue/src/tests/useAsgardeo.test.ts index 25506686..2b8d9b17 100644 --- a/packages/vue/src/tests/useAsgardeo.test.ts +++ b/packages/vue/src/tests/useAsgardeo.test.ts @@ -58,7 +58,7 @@ describe('useAsgardeo', () => { isSignedIn: true, isLoading: false, }, - trySignInSilently: vi.fn().mockResolvedValue(true), + signInSilently: vi.fn().mockResolvedValue(true), reInitialize: vi.fn().mockResolvedValue(undefined), }; diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index 4241a74c..bcfbb213 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -120,7 +120,7 @@ export interface AuthContextInterface { ) => Promise; signOut: (callback?: (response: boolean) => void) => Promise; state: AuthStateInterface; - trySignInSilently: ( + signInSilently: ( additionalParams?: Record, tokenRequestConfig?: {params: Record}, ) => Promise; From be9e94cf25ca3ee4abd67f7928c17f81c0617ebc Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 14:42:09 +0530 Subject: [PATCH 5/6] feat(react): implement signInSilently method in AsgardeoReactClient and integrate with context provider --- .../src/AsgardeoJavaScriptClient.ts | 2 ++ packages/javascript/src/models/client.ts | 13 ++++++++++- packages/nextjs/src/AsgardeoNextClient.ts | 9 ++++++++ packages/react/src/AsgardeoReactClient.ts | 6 +++++ packages/react/src/__temp__/api.ts | 21 ----------------- .../src/contexts/Asgardeo/AsgardeoContext.ts | 3 +++ .../contexts/Asgardeo/AsgardeoProvider.tsx | 23 +++++++++++++++++++ 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/packages/javascript/src/AsgardeoJavaScriptClient.ts b/packages/javascript/src/AsgardeoJavaScriptClient.ts index 1e58e4fa..97d3db18 100644 --- a/packages/javascript/src/AsgardeoJavaScriptClient.ts +++ b/packages/javascript/src/AsgardeoJavaScriptClient.ts @@ -66,6 +66,8 @@ abstract class AsgardeoJavaScriptClient implements AsgardeoClient onSignInSuccess?: (afterSignInUrl: string) => void, ): Promise; + abstract signInSilently(options?: SignInOptions): Promise; + abstract signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise; abstract signOut( options?: SignOutOptions, diff --git a/packages/javascript/src/models/client.ts b/packages/javascript/src/models/client.ts index 722714fa..ee497f55 100644 --- a/packages/javascript/src/models/client.ts +++ b/packages/javascript/src/models/client.ts @@ -63,7 +63,7 @@ export interface AsgardeoClient { * @param organization - The organization to switch to. * @returns A promise that resolves when the switch is complete. */ - switchOrganization(organization: Organization, sessionId?: string): Promise ; + switchOrganization(organization: Organization, sessionId?: string): Promise; getConfiguration(): T; @@ -137,6 +137,17 @@ export interface AsgardeoClient { onSignInSuccess?: (afterSignInUrl: string) => void, ): Promise; + /** + * Try signing in silently in the background without any user interactions. + * + * @remarks This approach uses a passive auth request (prompt=none) sent from an iframe which might pose issues in cross-origin scenarios. + * Make sure you are aware of the limitations and browser compatibility issues. + * + * @param options - Optional sign-in options like additional parameters to be sent in the authorize request, etc. + * @returns A promise that resolves to the user if sign-in is successful, or false if not. + */ + signInSilently(options?: SignInOptions): Promise; + /** * Signs out the currently signed-in user. * diff --git a/packages/nextjs/src/AsgardeoNextClient.ts b/packages/nextjs/src/AsgardeoNextClient.ts index 8e302b1a..5d083e60 100644 --- a/packages/nextjs/src/AsgardeoNextClient.ts +++ b/packages/nextjs/src/AsgardeoNextClient.ts @@ -488,6 +488,15 @@ class AsgardeoNextClient exte ); } + override signInSilently(options?: SignInOptions): Promise { + throw new AsgardeoRuntimeError( + 'Not implemented', + 'AsgardeoNextClient-signInSilently-NotImplementedError-001', + 'nextjs', + 'The signInSilently method is not implemented in the Next.js client.', + ); + } + /** * Gets the sign-in URL for authentication. * Ensures the client is initialized before making the call. diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index fcb46ea1..35fb6645 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -295,6 +295,12 @@ class AsgardeoReactClient e }); } + override async signInSilently(options?: SignInOptions): Promise { + return this.withLoading(async () => { + return this.asgardeo.signInSilently(options as Record); + }); + } + override signOut(options?: SignOutOptions, afterSignOut?: (afterSignOutUrl: string) => void): Promise; override signOut( options?: SignOutOptions, diff --git a/packages/react/src/__temp__/api.ts b/packages/react/src/__temp__/api.ts index 4d3bcfa4..d1784703 100644 --- a/packages/react/src/__temp__/api.ts +++ b/packages/react/src/__temp__/api.ts @@ -430,8 +430,6 @@ class AuthAPI { *``` */ public async signInSilently( - state: AuthStateInterface, - dispatch: (state: AuthStateInterface) => void, additionalParams?: Record, tokenRequestConfig?: {params: Record}, ): Promise { @@ -439,28 +437,9 @@ class AuthAPI { .signInSilently(additionalParams, tokenRequestConfig) .then(async (response: User | boolean) => { if (!response) { - this.updateState({...this.getState(), isLoading: false}); - dispatch({...state, isLoading: false}); - return false; } - if (await this._client.isSignedIn()) { - const basicUserInfo = response as User; - const stateToUpdate = { - displayName: basicUserInfo.displayName, - email: basicUserInfo.email, - isSignedIn: true, - isLoading: false, - isSigningOut: false, - username: basicUserInfo.username, - }; - - this.updateState(stateToUpdate); - - dispatch({...state, ...stateToUpdate}); - } - return response; }) .catch(error => Promise.reject(error)); diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts index e4eb9165..8f17d026 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts +++ b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts @@ -18,6 +18,7 @@ import {Context, createContext} from 'react'; import {Organization} from '@asgardeo/browser'; +import AsgardeoReactClient from '../../AsgardeoReactClient'; /** * Props interface of {@link AsgardeoContext} @@ -44,6 +45,7 @@ export type AsgardeoContextProps = { * TODO: Fix the types. */ signIn: any; + signInSilently: AsgardeoReactClient['signInSilently']; /** * Sign-out function to terminate the authentication session. * @remark This is the programmatic version of the `SignOutButton` component. @@ -75,6 +77,7 @@ const AsgardeoContext: Context = createContext> = ({ } }; + const signInSilently = async (options?: SignInOptions): Promise => { + try { + setIsLoadingSync(true); + const response: User | boolean = await asgardeo.signInSilently(options); + + if (await asgardeo.isSignedIn()) { + await updateSession(); + } + + return response; + } catch (error) { + throw new AsgardeoRuntimeError( + `Error while signing in silently: ${error.message || error}`, + 'asgardeo-signInSilently-Error', + 'react', + 'An error occurred while trying to sign in silently.', + ); + } finally { + setIsLoadingSync(asgardeo.isLoading()); + } + }; + const signUp = async (payload?: EmbeddedFlowExecuteRequestPayload): Promise => { try { return await asgardeo.signUp(payload); @@ -382,6 +404,7 @@ const AsgardeoProvider: FC> = ({ isSignedIn: isSignedInSync, organization: currentOrganization, signIn, + signInSilently, signOut, signUp, user, From cb918a30a4c195f0ca06f672d6146bbe4d555f27 Mon Sep 17 00:00:00 2001 From: Brion Date: Mon, 7 Jul 2025 14:46:25 +0530 Subject: [PATCH 6/6] chore: add changeset --- .changeset/tall-lemons-cheat.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/tall-lemons-cheat.md diff --git a/.changeset/tall-lemons-cheat.md b/.changeset/tall-lemons-cheat.md new file mode 100644 index 00000000..23920835 --- /dev/null +++ b/.changeset/tall-lemons-cheat.md @@ -0,0 +1,10 @@ +--- +'@asgardeo/browser': patch +'@asgardeo/javascript': patch +'@asgardeo/nextjs': patch +'@asgardeo/react': patch +'@asgardeo/react-router': patch +'@asgardeo/vue': patch +--- + +This update addresses issues in the `@asgardeo/react-router` package and exposes the `signInSilently` method from the `@asgardeo/react` package. The changes ensure that the `ProtectedRoute` component works correctly with the new sign-in functionality.