-
Notifications
You must be signed in to change notification settings - Fork 222
feat: architectural refactor for multi-platform extensibility #1233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: Arpit Jain <[email protected]> Co-authored-by: snyk-bot <[email protected]>
…act-native-auth0 into SDK-6110-initial-setup
…act-native-auth0 into SDK-6110-initial-setup
…ct-native-auth0 into SDK-6110-core-functionality
… using @auth0/auth0-spa-js
…into SDK-5762-rn-web-support
…tecture This commit lays the groundwork for the new, multi-platform architecture by introducing a set of clean, platform-agnostic types and interfaces. This establishes the core "contracts" that all future platform adapters (Native, Web, etc.) must adhere to, enabling a decoupled and extensible system. - **`src/types`**: Defines all public-facing data structures (`Credentials`, `User`) and parameter objects for API calls. Separates common types from platform-specific ones. - **`src/core/interfaces`**: Defines the high-level contracts for all major functionalities.
This commit adds the concrete implementations for our core data models and a set of shared, platform-agnostic utility functions. - **`src/core/models`**: Implements `AuthError`, `Auth0User`, and `Credentials` classes. These models encapsulate data and provide helper methods. - **`src/core/utils`**: Implements pure helper functions for validation, data conversion (camel-casing), and scope finalization.
This commit introduces the core business logic layer, which orchestrates authentication flows in a platform-agnostic way. These services operate exclusively on the previously defined interfaces and depend on an injected `HttpClient` to perform their work, making them completely decoupled from the underlying platform.
This commit introduces the factory layer, which is responsible for detecting the runtime environment and creating the appropriate platform-specific client. This is the central hub that connects the public API to the correct platform implementation.
This commit introduces the complete, self-contained module for the native (iOS/Android) platform. It defines the bridge for communicating with the native code and a set of adapters that implement the core library interfaces by delegating calls to the bridge.
This commit introduces the complete implementation for the web platform. It uses the Adapter pattern to wrap the `@auth0/auth0-spa-js` library, making it conform to our internal `IAuth0Client` interface and providing a consistent API surface.
This commit introduces the complete implementation for the web platform. It uses the Adapter pattern to wrap the `@auth0/auth0-spa-js` library, making it conform to our internal `IAuth0Client` interface and providing a consistent API surface.
This commit adds a full suite of co-located unit and integration tests for the new architecture, validating every layer from the core utilities to the platform adapters and hooks. All legacy test cases have been migrated to test their new component counterparts, ensuring functional parity. Global mocks are now handled via a `__mocks__` directory for a cleaner test setup.
- Deleted `addDefaultLocalAuthOptions.ts`, `baseError.ts`, `camel.ts`, `deepEqual.ts`, `fetchWithTimeout.ts`, `nativeHelper.ts`, `timestampConversion.ts`, `userConversion.ts`, and `whitelist.ts` as they are no longer needed. - Removed mock files for `auth0.js` and `react-native.js` as they are obsolete. - Cleared out test files `webauth.spec.js`, `agent.spec.js`, and snapshot files as they are no longer relevant. - Removed the `Agent` and `WebAuth` classes along with their associated methods and types, streamlining the authentication process.
- Created Button component for reusable button UI. - Added Header component for consistent header display. - Implemented LabeledInput component for labeled text input fields. - Developed Result component to display API call results and errors. - Created UserInfo component to show user profile information. - Set up AuthStackNavigator for unauthenticated user navigation. - Established ClassDemoNavigator for class-based demo navigation. - Implemented HooksDemoNavigator for hooks-based demo navigation. - Created MainTabNavigator for authenticated user tab navigation. - Developed RootNavigator to manage overall app navigation. - Added SelectionScreen for initial demo selection. - Implemented ClassApiTestsScreen for direct API testing in class-based demo. - Created ClassLoginScreen for class-based user login. - Developed ClassProfileScreen to display user profile in class-based demo. - Added ApiScreen for API calls in hooks-based demo. - Implemented HomeScreen for hooks-based demo login options. - Created MoreScreen for additional authentication methods in hooks-based demo. - Developed ProfileScreen to manage user profile and credentials in hooks-based demo.
…ing platform detection
…instantiation and session management
…ration guide for v5
…s and configuration
…hProvider to use Auth0Client directly and add unit tests for WebWebAuthProvider
…hProvider to use Auth0Client directly and add unit tests for WebWebAuthProvider
json = {}; | ||
} else { | ||
try { | ||
json = await response.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are trying to access response.json()
even when response.status
is 400
, 403,
or 500
. Is this expected ?
* @param params The object of parameters to convert. | ||
* @returns A URL-encoded query string. | ||
*/ | ||
export function toUrlQueryParams(params: Record<string, any>): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it handle already encoded query ?
Ignore this comment if this use-case is irrelevant here.
} | ||
|
||
private buildUrl(path: string, query?: Record<string, any>): string { | ||
let url = `${this.baseUrl}${path}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does baseUrl
always contain /
?
* @returns The current authentication state and methods. | ||
*/ | ||
export const useAuth0 = (): Auth0ContextInterface => { | ||
const context = useContext(Auth0Context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can useAuth0
be used anywhere ?
Or should we need to add a check like this ?
const context = useContext(Auth0Context);
if (context == null) {
throw new Error('useAuth0 must be used within Auth0Provider');
}
return context;
I don't know the complete spec here. Ignore if it's irrelevant.
* A private helper that converts a single snake_case or kebab-case string to camelCase. | ||
* e.g., 'user_profile' -> 'userProfile' | ||
*/ | ||
export function snakeToCamel(str: string): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think snakeToCamel('_snake_case_')
would return SnakeCase
here, but the expectation is snakeCase
.
Again, this is a very minor one 😅
const decodedToken = jwtDecode<any>(idToken); | ||
|
||
if (!decodedToken.sub) { | ||
throw new Error('ID token is missing the required "sub" claim.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we document this error in Typedoc
using @throws
?
* @returns A new Auth0User instance. | ||
*/ | ||
static fromIdToken(idToken: string): Auth0User { | ||
const decodedToken = jwtDecode<any>(idToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we handle the error thrown by jwtDecode
? So that developer can know exactly what went wrong ?
Ignore if it's already handled inside jwtDecode
.
* into a single, cohesive contract. Platform-specific factories will produce an | ||
* object that conforms to this interface. | ||
*/ | ||
export interface IAuth0Client { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These days we drop the I
on interface names and stick to plain PascalCase
(so Auth0Client
instead of IAuth0Client
). No need to fix it right now. Revisit this whenever you get time.
Reference: https://www.typescriptlang.org/docs/handbook/interfaces.html#interfaces
Summary & Motivation
This pull request introduces a major architectural refactor of the
react-native-auth0
library. The primary goal is to evolve from the existing tightly-coupled implementation to a highly abstracted, modular, and extensible architecture that cleanly supports multiple platforms (Native and Web) in a maintainable way.The previous structure made it difficult to add new features or platforms without significant code duplication and risk of regressions. This new architecture establishes a clear separation of concerns, making the library more robust, performant, and easier for future contributors to understand and extend.
The New Architecture
The new design is built on the Dependency Inversion Principle, using a Factory pattern to provide the correct platform-specific implementation at build time. High-level modules (like the React hooks) are now completely decoupled from low-level platform details (the native bridge or
auth0-spa-js
).High-Level Diagram
This diagram illustrates the new flow of control. The key concept is that the consumer-facing API is decoupled from the platform implementations via a Factory that chooses the correct module at build time.
Key Architectural Components
src/core
: The platform-agnostic heart of the library.interfaces
: Defines the "contract" for what our client can do (IAuth0Client
,IWebAuthProvider
, etc.). This is the cornerstone of the abstraction.models
: Concrete data models likeAuth0User
andCredentials
that encapsulate data and related logic (e.g.,isExpired()
).services
: "Orchestrator" classes that contain the business logic for authentication flows (e.g.,CredentialsOrchestrator
handles the token refresh flow).src/platforms
: Contains the platform-specific implementations.native
: The complete module for iOS and Android, containing theNativeBridgeManager
for low-level communication and a set ofadapters
that implement the core interfaces.web
: The complete module for React Native Web, containing a set ofadapters
that wrap the@auth0/auth0-spa-js
library to make it conform to our core interfaces.src/factory
: The "decision-making" layer that runs at build time..ts
,.web.ts
) to ensure the bundler (Metro/Webpack) includes only the code for the target platform. This completely severs the dependency on@auth0/auth0-spa-js
in native builds.src/hooks
&src/index.ts
(Public API):Auth0
class now acts as a simple Facade, which uses the factory to get the correct client. This maintains backward compatibility.Auth0Provider
uses this facade to power theuseAuth0
hook, providing a seamless and performant stateful experience. Issues with UI hanging on logout and infinite re-renders have been fixed.Key Breaking Changes for Users
A full
MIGRATION_GUIDE.md
has been created, but the most critical changes are:camelCase
Properties: Properties on theuser
object are nowcamelCase
(e.g.,user.givenName
instead ofuser.given_name
).expiresAt
Timestamp: TheCredentials
object now provides aexpiresAt
UNIX timestamp instead ofexpiresIn
.AuthError
: All errors are now instances of a single, consistentAuthError
class.authorize
now separate OIDC parameters and SDK options into two distinct arguments:authorize({scope}, {options})
.Testing Strategy
A comprehensive, co-located test suite has been implemented for the new architecture.
__mocks__
directory forreact-native
, creating a clean and stable test environment.How to Review This PR
Given the scope of the refactor, a commit-by-commit review is highly recommended. The commits have been structured logically to tell the story of the new architecture's construction:
feat(core): Introduce foundational types and interfaces...
: Establishes the core contracts.feat(core): Implement core data models and utility functions...
: Adds the reusable building blocks.feat(core): Implement platform-agnostic service orchestrators...
: Introduces the platform-agnostic business logic.feat(factory): Implement platform detector and client factory...
: Builds the platform-selection mechanism.feat(platform): Implement native platform...
: Adds the complete native implementation.feat(platform): Implement web platform...
: Adds the complete web implementation.feat(hooks): Implement public Auth0 facade and update hooks...
: Wires everything up to the consumer-facing API.test: Implement comprehensive test suite...
: Adds all the new tests.